[KEYCLOAK-3169] - UMA 2.0 (#4368)
* [KEYCLOAK-3169] - UMA 2.0 Support * [KEYCLOAK-3169] - Changes to account service and more tests * [KEYCLOAK-3169] - Code cleanup and tests * [KEYCLOAK-3169] - Changes to account service and tests * [KEYCLOAK-3169] - Changes to account service and tests * [KEYCLOAK-3169] - More tests * [KEYCLOAK-3169] - Changes to adapter configuration * [KEYCLOAK-3169] - Reviewing UMA specs and more tests * [KEYCLOAK-3169] - Reviewing UMA specs and more tests * [KEYCLOAK-3169] - Changes to UMA Grant Type and refactoring * [KEYCLOAK-3169] - Refresh tokens for RPT responses and tests * [KEYCLOAK-3169] - Changes to account my resources and policy enforcers * [KEYCLOAK-3169] - Realm settings flag to enable/disable user-managed access in account mgmt console * [KEYCLOAK-3169] - More changes to my resource pages in account mgmt console * [KEYCLOAK-3169] - Need to enable user-managed on realm to run tests * [KEYCLOAK-3169] - Removing more UMA 1.0 related code * [KEYCLOAK-3169] - Only submit requests if ticket exists * [KEYCLOAK-3169] - Returning UMA 401 response when not authenticated * [KEYCLOAK-3169] - Removing unused code * [KEYCLOAK-3169] - Removing unused code * [KEYCLOAK-3169] - 403 response in case ticket is not created * [KEYCLOAK-3169] - Fixing AbstractPhotozExampleAdapterTest#testClientRoleRepresentingUserConsent * [KEYCLOAK-3169] - 403 status code only returned for non-bearer clients
This commit is contained in:
parent
190ad06f1a
commit
91bdc4bde2
205 changed files with 8661 additions and 3389 deletions
|
@ -174,6 +174,10 @@ public class BearerTokenRequestAuthenticator {
|
|||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade facade) {
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
deployment.getPolicyEnforcer().enforce(OIDCHttpFacade.class.cast(facade));
|
||||
return true;
|
||||
}
|
||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
||||
facade.getRequest().setError(error);
|
||||
facade.getResponse().addHeader("WWW-Authenticate", challenge);
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -65,16 +64,21 @@ public abstract class AbstractPolicyEnforcer {
|
|||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext != null) {
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
Request request = httpFacade.getRequest();
|
||||
String path = getPath(request);
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
if (pathConfig != null) {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
|
||||
if (pathConfig == null) {
|
||||
|
@ -114,7 +118,6 @@ public abstract class AbstractPolicyEnforcer {
|
|||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
@ -139,7 +142,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||
boolean hasPermission = false;
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
if (permission.getResourceSetId() != null) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
|
@ -292,6 +295,6 @@ public abstract class AbstractPolicyEnforcer {
|
|||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -42,45 +42,40 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
challengeUmaAuthentication(pathConfig, methodConfig, facade);
|
||||
} else {
|
||||
challengeEntitlementAuthentication(facade);
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient);
|
||||
|
||||
if (ticket == null) {
|
||||
response.setStatus(403);
|
||||
return true;
|
||||
}
|
||||
|
||||
String realm = authzClient.getConfiguration().getRealm();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString();
|
||||
response.setStatus(401);
|
||||
StringBuilder wwwAuthenticate = new StringBuilder("UMA realm=\"").append(realm).append("\"").append(",as_uri=\"").append(authorizationServerUri).append("\"");
|
||||
|
||||
if (ticket != null) {
|
||||
wwwAuthenticate.append(",ticket=\"").append(ticket).append("\"");
|
||||
}
|
||||
|
||||
response.setHeader("WWW-Authenticate", wwwAuthenticate.toString());
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending UMA challenge");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending Entitlement challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private void challengeUmaAuthentication(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient);
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending UMA challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
return permission.forResource(permissionRequest).getTicket();
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -19,25 +19,23 @@ package org.keycloak.adapters.authorization;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||
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.representations.idm.authorization.PermissionResponse;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -90,6 +88,12 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||
|
||||
@Override
|
||||
protected void handleAccessDenied(OIDCHttpFacade facade) {
|
||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
|
||||
|
@ -103,45 +107,41 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
try {
|
||||
String accessToken = httpFacade.getSecurityContext().getTokenString();
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
String accessTokenString = securityContext.getTokenString();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
|
||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
AuthorizationRequest authzRequest;
|
||||
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
PermissionResponse permissionResponse = authzClient.protection().permission().create(permissionRequest);
|
||||
authzRequest = new AuthorizationRequest();
|
||||
authzRequest.setTicket(permissionResponse.getTicket());
|
||||
} else {
|
||||
authzRequest = new AuthorizationRequest();
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse = authzClient.authorization(accessTokenString).authorize(authzRequest);
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.debug("Obtaining entitlements for authenticated user.");
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
||||
request.addPermission(permissionRequest);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
} catch (AuthorizationDeniedException e) {
|
||||
LOGGER.debug("Authorization denied", e);
|
||||
return null;
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
|
@ -221,11 +221,11 @@ class PathMatcher {
|
|||
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = this.authzClient.protection().resource();
|
||||
Set<String> search = resource.findByFilter("uri=" + path);
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
@ -25,7 +34,6 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
|||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
|
@ -34,14 +42,6 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
|||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
@ -58,10 +58,15 @@ public class PolicyEnforcer {
|
|||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
|
||||
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(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
|
||||
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()));
|
||||
}
|
||||
}
|
||||
});
|
||||
this.pathMatcher = new PathMatcher(this.authzClient);
|
||||
|
@ -142,26 +147,34 @@ public class PolicyEnforcer {
|
|||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
Set<String> search;
|
||||
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);
|
||||
search = protectedResource.findByFilter("name=" + resourceName);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
search = protectedResource.findByFilter("uri=" + path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(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 (search.isEmpty()) {
|
||||
if (resource == null) {
|
||||
if (enforcerConfig.isCreateResources()) {
|
||||
LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
resource.setName(resourceName);
|
||||
resource.setType(pathConfig.getType());
|
||||
resource.setUri(path);
|
||||
representation.setName(resourceName);
|
||||
representation.setType(pathConfig.getType());
|
||||
representation.setUri(path);
|
||||
|
||||
HashSet<ScopeRepresentation> scopes = new HashSet<>();
|
||||
|
||||
|
@ -173,16 +186,16 @@ public class PolicyEnforcer {
|
|||
scopes.add(scope);
|
||||
}
|
||||
|
||||
resource.setScopes(scopes);
|
||||
representation.setScopes(scopes);
|
||||
|
||||
RegistrationResponse registrationResponse = protectedResource.create(resource);
|
||||
ResourceRepresentation registrationResponse = protectedResource.create(representation);
|
||||
|
||||
pathConfig.setId(registrationResponse.getId());
|
||||
} else {
|
||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
|
||||
}
|
||||
} else {
|
||||
pathConfig.setId(search.iterator().next());
|
||||
pathConfig.setId(resource.getId());
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
@ -210,8 +223,7 @@ public class PolicyEnforcer {
|
|||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
for (String id : protectedResource.findAll()) {
|
||||
RegistrationResponse response = protectedResource.findById(id);
|
||||
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUri() != null) {
|
||||
PathConfig pathConfig = createPathConfig(resourceDescription);
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
(function( window, undefined ) {
|
||||
|
||||
var KeycloakAuthorization = function (keycloak) {
|
||||
var KeycloakAuthorization = function (keycloak, options) {
|
||||
var _instance = this;
|
||||
this.rpt = null;
|
||||
|
||||
this.init = function () {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
if (request.status == 200) {
|
||||
|
@ -47,27 +47,21 @@
|
|||
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
|
||||
* depending on how the policy enforcer at the resource server was configured.
|
||||
*/
|
||||
this.authorize = function (wwwAuthenticateHeader) {
|
||||
this.authorize = function (authorizationRequest) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
|
||||
var params = wwwAuthenticateHeader.split(',');
|
||||
|
||||
for (i = 0; i < params.length; i++) {
|
||||
var param = params[i].split('=');
|
||||
|
||||
if (param[0] == 'ticket') {
|
||||
if (authorizationRequest && authorizationRequest.ticket) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.open('POST', _instance.config.rpt_endpoint, true);
|
||||
request.setRequestHeader('Content-Type', 'application/json')
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
request.open('POST', _instance.config.token_endpoint, true);
|
||||
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
var rpt = JSON.parse(request.responseText).access_token;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
|
@ -86,29 +80,28 @@
|
|||
}
|
||||
};
|
||||
|
||||
var ticket = param[1].substring(1, param[1].length - 1).trim();
|
||||
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
|
||||
|
||||
request.send(JSON.stringify(
|
||||
{
|
||||
ticket: ticket,
|
||||
rpt: _instance.rpt
|
||||
if (authorizationRequest.submitRequest != undefined) {
|
||||
params += "&submit_request=" + authorizationRequest.submitRequest;
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) {
|
||||
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
|
||||
var clientId = null;
|
||||
|
||||
for (i = 0; i < params.length; i++) {
|
||||
var param = params[i].split('=');
|
||||
var metadata = authorizationRequest.metadata;
|
||||
|
||||
if (param[0] == 'realm') {
|
||||
clientId = param[1].substring(1, param[1].length - 1).trim();
|
||||
if (metadata) {
|
||||
if (metadata.responseIncludeResourceName) {
|
||||
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
|
||||
}
|
||||
if (metadata.responsePermissionsLimit) {
|
||||
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
|
||||
}
|
||||
}
|
||||
|
||||
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
|
||||
if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
|
||||
params += "&rpt=" + _instance.rpt;
|
||||
}
|
||||
|
||||
request.send(params);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -116,20 +109,22 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
|
||||
*/
|
||||
this.entitlement = function (resourceSeververId, entitlementRequest ) {
|
||||
this.entitlement = function (resourceServerId, authorizationRequest) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
|
||||
request.open('POST', _instance.config.token_endpoint, true);
|
||||
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
var rpt = JSON.parse(request.responseText).access_token;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
|
@ -148,19 +143,62 @@
|
|||
}
|
||||
};
|
||||
|
||||
var erJson = null
|
||||
|
||||
if(entitlementRequest) {
|
||||
request.open('POST', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
erJson = JSON.stringify(entitlementRequest)
|
||||
} else {
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||
if (!authorizationRequest) {
|
||||
authorizationRequest = {};
|
||||
}
|
||||
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
request.send(erJson);
|
||||
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
|
||||
|
||||
if (authorizationRequest.claimToken) {
|
||||
params += "&claim_token=" + authorizationRequest.claimToken;
|
||||
|
||||
if (authorizationRequest.claimTokenFormat) {
|
||||
params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
|
||||
}
|
||||
}
|
||||
|
||||
params += "&audience=" + resourceServerId;
|
||||
|
||||
var permissions = authorizationRequest.permissions;
|
||||
|
||||
if (!permissions) {
|
||||
permissions = [];
|
||||
}
|
||||
|
||||
for (i = 0; i < permissions.length; i++) {
|
||||
var resource = permissions[i];
|
||||
var permission = resource.id;
|
||||
|
||||
if (resource.scopes && resource.scopes.length > 0) {
|
||||
permission += "#";
|
||||
for (j = 0; j < resource.scopes.length; j++) {
|
||||
var scope = resource.scopes[j];
|
||||
if (permission.indexOf('#') != permission.length - 1) {
|
||||
permission += ",";
|
||||
}
|
||||
permission += scope;
|
||||
}
|
||||
}
|
||||
|
||||
params += "&permission=" + permission;
|
||||
}
|
||||
|
||||
var metadata = authorizationRequest.metadata;
|
||||
|
||||
if (metadata) {
|
||||
if (metadata.responseIncludeResourceName) {
|
||||
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
|
||||
}
|
||||
if (metadata.responsePermissionsLimit) {
|
||||
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
|
||||
}
|
||||
}
|
||||
|
||||
if (_instance.rpt) {
|
||||
params += "&rpt=" + _instance.rpt;
|
||||
}
|
||||
|
||||
request.send(params);
|
||||
};
|
||||
|
||||
return this;
|
||||
|
|
|
@ -17,32 +17,40 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.authorization.client.resource.AuthorizationResource;
|
||||
import org.keycloak.authorization.client.resource.EntitlementResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.authorization.client.util.TokenCallable;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* <p>This is class serves as an entry point for clients looking for access to Keycloak Authorization Services.
|
||||
*
|
||||
* <p>When creating a new instances make sure you have a Keycloak Server running at the location specified in the client
|
||||
* configuration. The client tries to obtain server configuration by invoking the UMA Discovery Endpoint, usually available
|
||||
* from the server at <i>http(s)://{server}:{port}/auth/realms/{realm}/.well-known/uma-configuration</i>.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthzClient {
|
||||
|
||||
private final Http http;
|
||||
private Callable<String> patSupplier;
|
||||
private TokenCallable patSupplier;
|
||||
|
||||
public static AuthzClient create() {
|
||||
/**
|
||||
* <p>Creates a new instance.
|
||||
*
|
||||
* <p>This method expects a <code>keycloak.json</code> in the classpath, otherwise an exception will be thrown.
|
||||
*
|
||||
* @return a new instance
|
||||
* @throws RuntimeException in case there is no <code>keycloak.json</code> file in the classpath or the file could not be parsed
|
||||
*/
|
||||
public static AuthzClient create() throws RuntimeException {
|
||||
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("keycloak.json");
|
||||
|
||||
if (configStream == null) {
|
||||
|
@ -56,16 +64,158 @@ public class AuthzClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new instance.
|
||||
*
|
||||
* @param configuration the client configuration
|
||||
* @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);
|
||||
}
|
||||
|
||||
private final ServerConfiguration serverConfiguration;
|
||||
private final Configuration deployment;
|
||||
private final Configuration configuration;
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ProtectionResource} instance which can be used to access the Protection API.
|
||||
*
|
||||
* <p>When using this method, the PAT (the access token with the uma_protection scope) is obtained for the client
|
||||
* itself, using any of the supported credential types (client/secret, jwt, etc).
|
||||
*
|
||||
* @return a {@link ProtectionResource}
|
||||
*/
|
||||
public ProtectionResource protection() {
|
||||
return new ProtectionResource(this.http, this.serverConfiguration, createPatSupplier());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ProtectionResource} instance which can be used to access the Protection API.
|
||||
*
|
||||
* @param the PAT (the access token with the uma_protection scope)
|
||||
* @return a {@link ProtectionResource}
|
||||
*/
|
||||
public ProtectionResource protection(final String accessToken) {
|
||||
return new ProtectionResource(this.http, this.serverConfiguration, new TokenCallable(http, configuration, serverConfiguration) {
|
||||
@Override
|
||||
public String call() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRetry() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ProtectionResource} instance which can be used to access the Protection API.
|
||||
*
|
||||
* <p>When using this method, the PAT (the access token with the uma_protection scope) is obtained for a given user.
|
||||
*
|
||||
* @return a {@link ProtectionResource}
|
||||
*/
|
||||
public ProtectionResource protection(String userName, String password) {
|
||||
return new ProtectionResource(this.http, this.serverConfiguration, createPatSupplier(userName, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link AuthorizationResource} instance which can be used to obtain permissions from the server.
|
||||
*
|
||||
* @return a {@link AuthorizationResource}
|
||||
*/
|
||||
public AuthorizationResource authorization() {
|
||||
return new AuthorizationResource(configuration, serverConfiguration, this.http, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link AuthorizationResource} instance which can be used to obtain permissions from the server.
|
||||
*
|
||||
* @param accessToken the Access Token that will be used as a bearer to access the token endpoint
|
||||
* @return a {@link AuthorizationResource}
|
||||
*/
|
||||
public AuthorizationResource authorization(final String accessToken) {
|
||||
return new AuthorizationResource(configuration, serverConfiguration, this.http, new TokenCallable(http, configuration, serverConfiguration) {
|
||||
@Override
|
||||
public String call() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRetry() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link AuthorizationResource} instance which can be used to obtain permissions from the server.
|
||||
*
|
||||
* @param userName an ID Token or Access Token representing an identity and/or access context
|
||||
* @param password
|
||||
* @return a {@link AuthorizationResource}
|
||||
*/
|
||||
public AuthorizationResource authorization(final String userName, final String password) {
|
||||
return new AuthorizationResource(configuration, serverConfiguration, this.http, createRefreshableAccessTokenSupplier(userName, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an access token using the client credentials.
|
||||
*
|
||||
* @return an {@link AccessTokenResponse}
|
||||
*/
|
||||
public AccessTokenResponse obtainAccessToken() {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.client()
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an access token using the resource owner credentials.
|
||||
*
|
||||
* @return an {@link AccessTokenResponse}
|
||||
*/
|
||||
public AccessTokenResponse obtainAccessToken(String userName, String password) {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.oauth2ResourceOwnerPassword(userName, password)
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration obtained from the server at the UMA Discovery Endpoint.
|
||||
*
|
||||
* @return the {@link ServerConfiguration}
|
||||
*/
|
||||
public ServerConfiguration getServerConfiguration() {
|
||||
return this.serverConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the client configuration
|
||||
*
|
||||
* @return the {@link Configuration}
|
||||
*/
|
||||
public Configuration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
private AuthzClient(Configuration configuration, ClientAuthenticator authenticator) {
|
||||
if (configuration == null) {
|
||||
|
@ -78,14 +228,14 @@ public class AuthzClient {
|
|||
throw new IllegalArgumentException("Configuration URL can not be null.");
|
||||
}
|
||||
|
||||
configurationUrl += "/realms/" + configuration.getRealm() + "/.well-known/uma-configuration";
|
||||
configurationUrl += "/realms/" + configuration.getRealm() + "/.well-known/uma2-configuration";
|
||||
|
||||
this.deployment = configuration;
|
||||
this.configuration = configuration;
|
||||
|
||||
this.http = new Http(configuration, authenticator != null ? authenticator : configuration.getClientAuthenticator());
|
||||
|
||||
try {
|
||||
this.serverConfiguration = this.http.<ServerConfiguration>get(URI.create(configurationUrl))
|
||||
this.serverConfiguration = this.http.<ServerConfiguration>get(configurationUrl)
|
||||
.response().json(ServerConfiguration.class)
|
||||
.execute();
|
||||
} catch (Exception e) {
|
||||
|
@ -95,85 +245,18 @@ public class AuthzClient {
|
|||
this.http.setServerConfiguration(this.serverConfiguration);
|
||||
}
|
||||
|
||||
private AuthzClient(Configuration configuration) {
|
||||
this(configuration, null);
|
||||
}
|
||||
|
||||
public ProtectionResource protection() {
|
||||
return new ProtectionResource(this.http, createPatSupplier());
|
||||
}
|
||||
|
||||
public AuthorizationResource authorization(String accesstoken) {
|
||||
return new AuthorizationResource(this.http, accesstoken);
|
||||
}
|
||||
|
||||
public AuthorizationResource authorization(String userName, String password) {
|
||||
return new AuthorizationResource(this.http, obtainAccessToken(userName, password).getToken());
|
||||
}
|
||||
|
||||
public EntitlementResource entitlement(String eat) {
|
||||
return new EntitlementResource(this.http, eat);
|
||||
}
|
||||
|
||||
public AccessTokenResponse obtainAccessToken() {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.client()
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public AccessTokenResponse obtainAccessToken(String userName, String password) {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.oauth2ResourceOwnerPassword(userName, password)
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public ServerConfiguration getServerConfiguration() {
|
||||
return this.serverConfiguration;
|
||||
}
|
||||
|
||||
public Configuration getConfiguration() {
|
||||
return this.deployment;
|
||||
}
|
||||
|
||||
private Callable<String> createPatSupplier() {
|
||||
private TokenCallable createPatSupplier(String userName, String password) {
|
||||
if (patSupplier == null) {
|
||||
patSupplier = new Callable<String>() {
|
||||
AccessTokenResponse clientToken = obtainAccessToken();
|
||||
|
||||
@Override
|
||||
public String call() {
|
||||
String token = clientToken.getToken();
|
||||
|
||||
try {
|
||||
AccessToken accessToken = JsonSerialization.readValue(new JWSInput(token).getContent(), AccessToken.class);
|
||||
|
||||
if (accessToken.isActive()) {
|
||||
return token;
|
||||
}
|
||||
|
||||
clientToken = http.<AccessTokenResponse>post(serverConfiguration.getTokenEndpoint())
|
||||
.authentication().client()
|
||||
.form()
|
||||
.param("grant_type", "refresh_token")
|
||||
.param("refresh_token", clientToken.getRefreshToken())
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
} catch (Exception e) {
|
||||
patSupplier = null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return clientToken.getToken();
|
||||
}
|
||||
};
|
||||
patSupplier = createRefreshableAccessTokenSupplier(userName, password);
|
||||
}
|
||||
return patSupplier;
|
||||
}
|
||||
|
||||
private TokenCallable createPatSupplier() {
|
||||
return createPatSupplier(null, null);
|
||||
}
|
||||
|
||||
private TokenCallable createRefreshableAccessTokenSupplier(final String userName, final String password) {
|
||||
return new TokenCallable(userName, password, http, configuration, serverConfiguration);
|
||||
}
|
||||
}
|
|
@ -17,11 +17,12 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClientAuthenticator {
|
||||
void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders);
|
||||
void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders);
|
||||
}
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
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.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -34,10 +34,22 @@ public class Configuration extends AdapterConfig {
|
|||
@JsonIgnore
|
||||
private HttpClient httpClient;
|
||||
|
||||
@JsonIgnore
|
||||
private ClientAuthenticator clientAuthenticator = createDefaultClientAuthenticator();
|
||||
|
||||
public Configuration() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param authServerUrl the server's URL. E.g.: http://{server}:{port}/auth.(not {@code null})
|
||||
* @param realm the realm name (not {@code null})
|
||||
* @param clientId the client id (not {@code null})
|
||||
* @param clientCredentials a map with the client credentials (not {@code null})
|
||||
* @param httpClient the {@link HttpClient} instance that should be used when sending requests to the server, or {@code null} if a default instance should be created
|
||||
*/
|
||||
public Configuration(String authServerUrl, String realm, String clientId, Map<String, Object> clientCredentials, HttpClient httpClient) {
|
||||
this.authServerUrl = authServerUrl;
|
||||
setAuthServerUrl(authServerUrl);
|
||||
|
@ -47,10 +59,26 @@ public class Configuration extends AdapterConfig {
|
|||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private ClientAuthenticator clientAuthenticator = new ClientAuthenticator() {
|
||||
public HttpClient getHttpClient() {
|
||||
if (this.httpClient == null) {
|
||||
this.httpClient = HttpClients.createDefault();
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
ClientAuthenticator getClientAuthenticator() {
|
||||
return this.clientAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
|
||||
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
String secret = (String) getCredentials().get("secret");
|
||||
|
||||
if (secret == null) {
|
||||
|
@ -60,16 +88,5 @@ public class Configuration extends AdapterConfig {
|
|||
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(getResource(), secret));
|
||||
}
|
||||
};
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
if (this.httpClient == null) {
|
||||
this.httpClient = HttpClients.createDefault();
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public ClientAuthenticator getClientAuthenticator() {
|
||||
return this.clientAuthenticator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.representation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationRequest {
|
||||
|
||||
private String ticket;
|
||||
private String rpt;
|
||||
|
||||
public AuthorizationRequest(String ticket, String rpt) {
|
||||
this.ticket = ticket;
|
||||
this.rpt = rpt;
|
||||
}
|
||||
|
||||
public AuthorizationRequest(String ticket) {
|
||||
this(ticket, null);
|
||||
}
|
||||
|
||||
public AuthorizationRequest() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return this.ticket;
|
||||
}
|
||||
|
||||
public String getRpt() {
|
||||
return this.rpt;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.representation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationResponse {
|
||||
|
||||
private String rpt;
|
||||
|
||||
public AuthorizationResponse(String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
|
||||
public AuthorizationResponse() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public String getRpt() {
|
||||
return this.rpt;
|
||||
}
|
||||
|
||||
public void setRpt(final String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package org.keycloak.authorization.client.representation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>An {@code {@link EntitlementRequest} represents a request sent to the server containing the permissions being requested.
|
||||
*
|
||||
* <p>Along with an entitlement request additional {@link AuthorizationRequestMetadata} information can be passed in order to define what clients expect from
|
||||
* the server when evaluating the requested permissions and when returning with a response.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class EntitlementRequest {
|
||||
|
||||
private String rpt;
|
||||
private AuthorizationRequestMetadata metadata;
|
||||
|
||||
private List<PermissionRequest> permissions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns the permissions being requested.
|
||||
*
|
||||
* @return the permissions being requested (not {@code null})
|
||||
*/
|
||||
public List<PermissionRequest> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the permissions being requested
|
||||
*
|
||||
* @param permissions the permissions being requests (not {@code null})
|
||||
*/
|
||||
public void setPermissions(List<PermissionRequest> permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link PermissionRequest} to the list of requested permissions.
|
||||
*
|
||||
* @param request the permission to request (not {@code null})
|
||||
*/
|
||||
public void addPermission(PermissionRequest request) {
|
||||
getPermissions().add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
|
||||
*
|
||||
* @return a previously issued RPT (may be {@code null})
|
||||
*/
|
||||
public String getRpt() {
|
||||
return rpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
|
||||
*
|
||||
* @param rpt a previously issued RPT. If {@code null}, only the requested permissions are evaluated
|
||||
*/
|
||||
public void setRpt(String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Metadata} associated with this request.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AuthorizationRequestMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Metadata} associated with this request. The metadata defines specific information that should be considered
|
||||
* by the server when evaluating and returning permissions.
|
||||
*
|
||||
* @param metadata the {@link Metadata} associated with this request (may be {@code null})
|
||||
*/
|
||||
public void setMetadata(AuthorizationRequestMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.representation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ErrorResponse {
|
||||
|
||||
private String error;
|
||||
|
||||
@JsonProperty("error_description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("error_uri")
|
||||
private String uri;
|
||||
|
||||
public ErrorResponse(final String error, final String description, final String uri) {
|
||||
this.error = error;
|
||||
this.description = description;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public ErrorResponse(final String error) {
|
||||
this(error, null, null);
|
||||
}
|
||||
|
||||
public ErrorResponse() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.representation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionRequest {
|
||||
|
||||
@JsonProperty("resource_set_id")
|
||||
private String resourceSetId;
|
||||
|
||||
@JsonProperty("resource_set_name")
|
||||
private String resourceSetName;
|
||||
|
||||
private Set<String> scopes;
|
||||
|
||||
public PermissionRequest() {
|
||||
|
||||
}
|
||||
|
||||
public PermissionRequest(String resourceSetId, String resourceSetName, Set<String> scopes) {
|
||||
this.resourceSetId = resourceSetId;
|
||||
this.resourceSetName = resourceSetName;
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public PermissionRequest(String resourceSetName) {
|
||||
this.resourceSetName = resourceSetName;
|
||||
}
|
||||
|
||||
public PermissionRequest(String resourceSetName, Set<String> scopes) {
|
||||
this.resourceSetName = resourceSetName;
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public String getResourceSetId() {
|
||||
return this.resourceSetId;
|
||||
}
|
||||
|
||||
public void setResourceSetId(String resourceSetId) {
|
||||
this.resourceSetId = resourceSetId;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public String getResourceSetName() {
|
||||
return this.resourceSetName;
|
||||
}
|
||||
|
||||
public void setResourceSetName(String resourceSetName) {
|
||||
this.resourceSetName = resourceSetName;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.representation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class RegistrationResponse {
|
||||
|
||||
private final ResourceRepresentation resourceDescription;
|
||||
|
||||
public RegistrationResponse(ResourceRepresentation resourceDescription) {
|
||||
this.resourceDescription = resourceDescription;
|
||||
}
|
||||
|
||||
public RegistrationResponse() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
@JsonUnwrapped
|
||||
public ResourceRepresentation getResourceDescription() {
|
||||
return this.resourceDescription;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
if (this.resourceDescription != null) {
|
||||
return this.resourceDescription.getId();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -17,14 +17,14 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* <p>One or more resources that the resource server manages as a set of protected resources.
|
||||
*
|
||||
|
@ -38,13 +38,17 @@ public class ResourceRepresentation {
|
|||
private String id;
|
||||
|
||||
private String name;
|
||||
private String displayName;
|
||||
private String uri;
|
||||
private String type;
|
||||
|
||||
@JsonProperty("resource_scopes")
|
||||
private Set<ScopeRepresentation> scopes;
|
||||
|
||||
@JsonProperty("icon_uri")
|
||||
private String iconUri;
|
||||
private String owner;
|
||||
private Boolean ownerManagedAccess;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
|
@ -106,6 +110,10 @@ public class ResourceRepresentation {
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
@ -129,6 +137,10 @@ public class ResourceRepresentation {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setUri(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
@ -153,6 +165,14 @@ public class ResourceRepresentation {
|
|||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void setOwnerManagedAccess(Boolean ownerManagedAccess) {
|
||||
this.ownerManagedAccess = ownerManagedAccess;
|
||||
}
|
||||
|
||||
public Boolean getOwnerManagedAccess() {
|
||||
return ownerManagedAccess;
|
||||
}
|
||||
|
||||
public void addScope(ScopeRepresentation scopeRepresentation) {
|
||||
if (this.scopes == null) {
|
||||
this.scopes = new HashSet<>();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
* Copyright 2018 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.
|
||||
|
@ -17,219 +16,194 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ServerConfiguration {
|
||||
|
||||
private String version;
|
||||
private URI issuer;
|
||||
|
||||
@JsonProperty("pat_profiles_supported")
|
||||
private Set<String> patProfiles;
|
||||
|
||||
@JsonProperty("pat_grant_types_supported")
|
||||
private Set<String> patGrantTypes;
|
||||
|
||||
@JsonProperty("aat_profiles_supported")
|
||||
private Set<String> aatProfiles;
|
||||
|
||||
@JsonProperty("aat_grant_types_supported")
|
||||
private Set<String> aatGrantTypes;
|
||||
|
||||
@JsonProperty("rpt_profiles_supported")
|
||||
private Set<String> rptProfiles;
|
||||
|
||||
@JsonProperty("claim_token_profiles_supported")
|
||||
private Set<String> claimTokenProfiles;
|
||||
|
||||
@JsonProperty("dynamic_client_endpoint")
|
||||
private URI dynamicClientEndpoint;
|
||||
|
||||
@JsonProperty("token_endpoint")
|
||||
private URI tokenEndpoint;
|
||||
@JsonProperty("issuer")
|
||||
private String issuer;
|
||||
|
||||
@JsonProperty("authorization_endpoint")
|
||||
private URI authorizationEndpoint;
|
||||
private String authorizationEndpoint;
|
||||
|
||||
@JsonProperty("requesting_party_claims_endpoint")
|
||||
private URI requestingPartyClaimsEndpoint;
|
||||
@JsonProperty("token_endpoint")
|
||||
private String tokenEndpoint;
|
||||
|
||||
@JsonProperty("resource_set_registration_endpoint")
|
||||
private URI resourceSetRegistrationEndpoint;
|
||||
@JsonProperty("token_introspection_endpoint")
|
||||
private String tokenIntrospectionEndpoint;
|
||||
|
||||
@JsonProperty("introspection_endpoint")
|
||||
private URI introspectionEndpoint;
|
||||
@JsonProperty("userinfo_endpoint")
|
||||
private String userinfoEndpoint;
|
||||
|
||||
@JsonProperty("permission_registration_endpoint")
|
||||
private URI permissionRegistrationEndpoint;
|
||||
@JsonProperty("end_session_endpoint")
|
||||
private String logoutEndpoint;
|
||||
|
||||
@JsonProperty("rpt_endpoint")
|
||||
private URI rptEndpoint;
|
||||
@JsonProperty("jwks_uri")
|
||||
private String jwksUri;
|
||||
|
||||
/**
|
||||
* Non-standard, Keycloak specific configuration options
|
||||
*/
|
||||
private String realm;
|
||||
@JsonProperty("check_session_iframe")
|
||||
private String checkSessionIframe;
|
||||
|
||||
private String realmPublicKey;
|
||||
@JsonProperty("grant_types_supported")
|
||||
private List<String> grantTypesSupported;
|
||||
|
||||
private URI serverUrl;
|
||||
@JsonProperty("response_types_supported")
|
||||
private List<String> responseTypesSupported;
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
@JsonProperty("subject_types_supported")
|
||||
private List<String> subjectTypesSupported;
|
||||
|
||||
@JsonProperty("id_token_signing_alg_values_supported")
|
||||
private List<String> idTokenSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("userinfo_signing_alg_values_supported")
|
||||
private List<String> userInfoSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("request_object_signing_alg_values_supported")
|
||||
private List<String> requestObjectSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("response_modes_supported")
|
||||
private List<String> responseModesSupported;
|
||||
|
||||
@JsonProperty("registration_endpoint")
|
||||
private String registrationEndpoint;
|
||||
|
||||
@JsonProperty("token_endpoint_auth_methods_supported")
|
||||
private List<String> tokenEndpointAuthMethodsSupported;
|
||||
|
||||
@JsonProperty("token_endpoint_auth_signing_alg_values_supported")
|
||||
private List<String> tokenEndpointAuthSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("claims_supported")
|
||||
private List<String> claimsSupported;
|
||||
|
||||
@JsonProperty("claim_types_supported")
|
||||
private List<String> claimTypesSupported;
|
||||
|
||||
@JsonProperty("claims_parameter_supported")
|
||||
private Boolean claimsParameterSupported;
|
||||
|
||||
@JsonProperty("scopes_supported")
|
||||
private List<String> scopesSupported;
|
||||
|
||||
@JsonProperty("request_parameter_supported")
|
||||
private Boolean requestParameterSupported;
|
||||
|
||||
@JsonProperty("request_uri_parameter_supported")
|
||||
private Boolean requestUriParameterSupported;
|
||||
|
||||
@JsonProperty("resource_registration_endpoint")
|
||||
private String resourceRegistrationEndpoint;
|
||||
|
||||
@JsonProperty("permission_endpoint")
|
||||
private String permissionEndpoint;
|
||||
|
||||
public String getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
void setVersion(final String version) {
|
||||
this.version = version;
|
||||
public String getAuthorizationEndpoint() {
|
||||
return authorizationEndpoint;
|
||||
}
|
||||
|
||||
public URI getIssuer() {
|
||||
return this.issuer;
|
||||
public String getTokenEndpoint() {
|
||||
return tokenEndpoint;
|
||||
}
|
||||
|
||||
void setIssuer(final URI issuer) {
|
||||
this.issuer = issuer;
|
||||
public String getTokenIntrospectionEndpoint() {
|
||||
return tokenIntrospectionEndpoint;
|
||||
}
|
||||
|
||||
public Set<String> getPatProfiles() {
|
||||
return this.patProfiles;
|
||||
public String getUserinfoEndpoint() {
|
||||
return userinfoEndpoint;
|
||||
}
|
||||
|
||||
void setPatProfiles(final Set<String> patProfiles) {
|
||||
this.patProfiles = patProfiles;
|
||||
public String getLogoutEndpoint() {
|
||||
return logoutEndpoint;
|
||||
}
|
||||
|
||||
public Set<String> getPatGrantTypes() {
|
||||
return this.patGrantTypes;
|
||||
public String getJwksUri() {
|
||||
return jwksUri;
|
||||
}
|
||||
|
||||
void setPatGrantTypes(final Set<String> patGrantTypes) {
|
||||
this.patGrantTypes = patGrantTypes;
|
||||
public String getCheckSessionIframe() {
|
||||
return checkSessionIframe;
|
||||
}
|
||||
|
||||
public Set<String> getAatProfiles() {
|
||||
return this.aatProfiles;
|
||||
public List<String> getGrantTypesSupported() {
|
||||
return grantTypesSupported;
|
||||
}
|
||||
|
||||
void setAatProfiles(final Set<String> aatProfiles) {
|
||||
this.aatProfiles = aatProfiles;
|
||||
public List<String> getResponseTypesSupported() {
|
||||
return responseTypesSupported;
|
||||
}
|
||||
|
||||
public Set<String> getAatGrantTypes() {
|
||||
return this.aatGrantTypes;
|
||||
public List<String> getSubjectTypesSupported() {
|
||||
return subjectTypesSupported;
|
||||
}
|
||||
|
||||
void setAatGrantTypes(final Set<String> aatGrantTypes) {
|
||||
this.aatGrantTypes = aatGrantTypes;
|
||||
public List<String> getIdTokenSigningAlgValuesSupported() {
|
||||
return idTokenSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public Set<String> getRptProfiles() {
|
||||
return this.rptProfiles;
|
||||
public List<String> getUserInfoSigningAlgValuesSupported() {
|
||||
return userInfoSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
void setRptProfiles(final Set<String> rptProfiles) {
|
||||
this.rptProfiles = rptProfiles;
|
||||
public List<String> getRequestObjectSigningAlgValuesSupported() {
|
||||
return requestObjectSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public Set<String> getClaimTokenProfiles() {
|
||||
return this.claimTokenProfiles;
|
||||
public List<String> getResponseModesSupported() {
|
||||
return responseModesSupported;
|
||||
}
|
||||
|
||||
void setClaimTokenProfiles(final Set<String> claimTokenProfiles) {
|
||||
this.claimTokenProfiles = claimTokenProfiles;
|
||||
public String getRegistrationEndpoint() {
|
||||
return registrationEndpoint;
|
||||
}
|
||||
|
||||
public URI getDynamicClientEndpoint() {
|
||||
return this.dynamicClientEndpoint;
|
||||
public List<String> getTokenEndpointAuthMethodsSupported() {
|
||||
return tokenEndpointAuthMethodsSupported;
|
||||
}
|
||||
|
||||
void setDynamicClientEndpoint(final URI dynamicClientEndpoint) {
|
||||
this.dynamicClientEndpoint = dynamicClientEndpoint;
|
||||
public List<String> getTokenEndpointAuthSigningAlgValuesSupported() {
|
||||
return tokenEndpointAuthSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public URI getTokenEndpoint() {
|
||||
return this.tokenEndpoint;
|
||||
public List<String> getClaimsSupported() {
|
||||
return claimsSupported;
|
||||
}
|
||||
|
||||
void setTokenEndpoint(final URI tokenEndpoint) {
|
||||
this.tokenEndpoint = tokenEndpoint;
|
||||
public List<String> getClaimTypesSupported() {
|
||||
return claimTypesSupported;
|
||||
}
|
||||
|
||||
public URI getAuthorizationEndpoint() {
|
||||
return this.authorizationEndpoint;
|
||||
public Boolean getClaimsParameterSupported() {
|
||||
return claimsParameterSupported;
|
||||
}
|
||||
|
||||
void setAuthorizationEndpoint(final URI authorizationEndpoint) {
|
||||
this.authorizationEndpoint = authorizationEndpoint;
|
||||
public List<String> getScopesSupported() {
|
||||
return scopesSupported;
|
||||
}
|
||||
|
||||
public URI getRequestingPartyClaimsEndpoint() {
|
||||
return this.requestingPartyClaimsEndpoint;
|
||||
public Boolean getRequestParameterSupported() {
|
||||
return requestParameterSupported;
|
||||
}
|
||||
|
||||
void setRequestingPartyClaimsEndpoint(final URI requestingPartyClaimsEndpoint) {
|
||||
this.requestingPartyClaimsEndpoint = requestingPartyClaimsEndpoint;
|
||||
public Boolean getRequestUriParameterSupported() {
|
||||
return requestUriParameterSupported;
|
||||
}
|
||||
|
||||
public URI getResourceSetRegistrationEndpoint() {
|
||||
return this.resourceSetRegistrationEndpoint;
|
||||
public String getResourceRegistrationEndpoint() {
|
||||
return resourceRegistrationEndpoint;
|
||||
}
|
||||
|
||||
void setResourceSetRegistrationEndpoint(final URI resourceSetRegistrationEndpoint) {
|
||||
this.resourceSetRegistrationEndpoint = resourceSetRegistrationEndpoint;
|
||||
}
|
||||
|
||||
public URI getIntrospectionEndpoint() {
|
||||
return this.introspectionEndpoint;
|
||||
}
|
||||
|
||||
void setIntrospectionEndpoint(final URI introspectionEndpoint) {
|
||||
this.introspectionEndpoint = introspectionEndpoint;
|
||||
}
|
||||
|
||||
public URI getPermissionRegistrationEndpoint() {
|
||||
return this.permissionRegistrationEndpoint;
|
||||
}
|
||||
|
||||
void setPermissionRegistrationEndpoint(final URI permissionRegistrationEndpoint) {
|
||||
this.permissionRegistrationEndpoint = permissionRegistrationEndpoint;
|
||||
}
|
||||
|
||||
public URI getRptEndpoint() {
|
||||
return this.rptEndpoint;
|
||||
}
|
||||
|
||||
void setRptEndpoint(final URI rptEndpoint) {
|
||||
this.rptEndpoint = rptEndpoint;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return this.realm;
|
||||
}
|
||||
|
||||
public void setRealm(final String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getRealmPublicKey() {
|
||||
return this.realmPublicKey;
|
||||
}
|
||||
|
||||
public void setRealmPublicKey(String realmPublicKey) {
|
||||
this.realmPublicKey = realmPublicKey;
|
||||
}
|
||||
|
||||
public URI getServerUrl() {
|
||||
return this.serverUrl;
|
||||
}
|
||||
|
||||
public void setServerUrl(URI serverUrl) {
|
||||
this.serverUrl = serverUrl;
|
||||
public String getPermissionEndpoint() {
|
||||
return permissionEndpoint;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
|
|
@ -18,34 +18,82 @@
|
|||
package org.keycloak.authorization.client.resource;
|
||||
|
||||
|
||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.authorization.client.util.HttpMethod;
|
||||
import org.keycloak.authorization.client.util.Throwables;
|
||||
import org.keycloak.authorization.client.util.TokenCallable;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
|
||||
/**
|
||||
* An entry point for obtaining permissions from the server.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationResource {
|
||||
|
||||
private final Http http;
|
||||
private final String accessToken;
|
||||
private Configuration configuration;
|
||||
private ServerConfiguration serverConfiguration;
|
||||
private Http http;
|
||||
private TokenCallable token;
|
||||
|
||||
public AuthorizationResource(Http http, String aat) {
|
||||
public AuthorizationResource(Configuration configuration, ServerConfiguration serverConfiguration, Http http, TokenCallable token) {
|
||||
this.configuration = configuration;
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
this.http = http;
|
||||
this.accessToken = aat;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public AuthorizationResponse authorize(AuthorizationRequest request) {
|
||||
/**
|
||||
* Query the server for all permissions.
|
||||
*
|
||||
* @return an {@link AuthorizationResponse} with a RPT holding all granted permissions
|
||||
* @throws AuthorizationDeniedException in case the request was denied by the server
|
||||
*/
|
||||
public AuthorizationResponse authorize() throws AuthorizationDeniedException {
|
||||
return authorize(new AuthorizationRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for permissions given an {@link AuthorizationRequest}.
|
||||
*
|
||||
* @param request an {@link AuthorizationRequest} (not {@code null})
|
||||
* @return an {@link AuthorizationResponse} with a RPT holding all granted permissions
|
||||
* @throws AuthorizationDeniedException in case the request was denied by the server
|
||||
*/
|
||||
public AuthorizationResponse authorize(final AuthorizationRequest request) throws AuthorizationDeniedException {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("Authorization request must not be null");
|
||||
}
|
||||
|
||||
Callable<AuthorizationResponse> callable = new Callable<AuthorizationResponse>() {
|
||||
@Override
|
||||
public AuthorizationResponse call() throws Exception {
|
||||
request.setAudience(configuration.getResource());
|
||||
|
||||
HttpMethod<AuthorizationResponse> method = http.<AuthorizationResponse>post(serverConfiguration.getTokenEndpoint());
|
||||
|
||||
if (token != null) {
|
||||
method = method.authorizationBearer(token.call());
|
||||
}
|
||||
|
||||
return method
|
||||
.authentication()
|
||||
.uma(request)
|
||||
.response()
|
||||
.json(AuthorizationResponse.class)
|
||||
.execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return this.http.<AuthorizationResponse>post("/authz/authorize")
|
||||
.authorizationBearer(this.accessToken)
|
||||
.json(JsonSerialization.writeValueAsBytes(request))
|
||||
.response().json(AuthorizationResponse.class).execute();
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Failed to obtain authorization data", cause);
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, token, "Failed to obtain authorization data", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package org.keycloak.authorization.client.resource;
|
||||
|
||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
||||
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class EntitlementResource {
|
||||
|
||||
private final Http http;
|
||||
private final String eat;
|
||||
|
||||
public EntitlementResource(Http http, String eat) {
|
||||
this.http = http;
|
||||
this.eat = eat;
|
||||
}
|
||||
|
||||
public EntitlementResponse getAll(String resourceServerId) {
|
||||
try {
|
||||
return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
|
||||
.authorizationBearer(eat)
|
||||
.response().json(EntitlementResponse.class).execute();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Failed to obtain entitlements", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public EntitlementResponse get(String resourceServerId, EntitlementRequest request) {
|
||||
try {
|
||||
return this.http.<EntitlementResponse>post("/authz/entitlement/" + resourceServerId)
|
||||
.authorizationBearer(eat)
|
||||
.json(JsonSerialization.writeValueAsBytes(request))
|
||||
.response().json(EntitlementResponse.class).execute();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Failed to obtain entitlements", cause);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,36 +17,205 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.resource;
|
||||
|
||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.authorization.client.util.Throwables;
|
||||
import org.keycloak.authorization.client.util.TokenCallable;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.representations.idm.authorization.PermissionResponse;
|
||||
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* An entry point for managing permission tickets using the Protection API.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionResource {
|
||||
|
||||
private final Http http;
|
||||
private final Callable<String> pat;
|
||||
private final ServerConfiguration serverConfiguration;
|
||||
private final TokenCallable pat;
|
||||
|
||||
public PermissionResource(Http http, Callable<String> pat) {
|
||||
public PermissionResource(Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
|
||||
this.http = http;
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
this.pat = pat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #create(PermissionRequest)}
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public PermissionResponse forResource(PermissionRequest request) {
|
||||
try {
|
||||
return this.http.<PermissionResponse>post("/authz/protection/permission")
|
||||
.authorizationBearer(this.pat.call())
|
||||
.json(JsonSerialization.writeValueAsBytes(request))
|
||||
return create(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permission ticket for a single resource and scope(s).
|
||||
*
|
||||
* @param request the {@link PermissionRequest} representing the resource and scope(s) (not {@code null})
|
||||
* @return a permission response holding a permission ticket with the requested permissions
|
||||
*/
|
||||
public PermissionResponse create(PermissionRequest request) {
|
||||
return create(Arrays.asList(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permission ticket for a set of one or more resource and scope(s).
|
||||
*
|
||||
* @param request the {@link PermissionRequest} representing the resource and scope(s) (not {@code null})
|
||||
* @return a permission response holding a permission ticket with the requested permissions
|
||||
*/
|
||||
public PermissionResponse create(final List<PermissionRequest> requests) {
|
||||
if (requests == null || requests.isEmpty()) {
|
||||
throw new IllegalArgumentException("Permission request must not be null or empty");
|
||||
}
|
||||
Callable<PermissionResponse> callable = new Callable<PermissionResponse>() {
|
||||
@Override
|
||||
public PermissionResponse call() throws Exception {
|
||||
return http.<PermissionResponse>post(serverConfiguration.getPermissionEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.json(JsonSerialization.writeValueAsBytes(requests))
|
||||
.response().json(PermissionResponse.class).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Error obtaining permission ticket", cause);
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error creating permission ticket", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for any permission ticket associated with the given <code>scopeId</code>.
|
||||
*
|
||||
* @param scopeId the scope id (not {@code null})
|
||||
* @return a list of permission tickets associated with the given <code>scopeId</code>
|
||||
*/
|
||||
public List<PermissionTicketRepresentation> findByScope(final String scopeId) {
|
||||
if (scopeId == null) {
|
||||
throw new IllegalArgumentException("Scope id must not be null");
|
||||
}
|
||||
Callable<List<PermissionTicketRepresentation>> callable = new Callable<List<PermissionTicketRepresentation>>() {
|
||||
@Override
|
||||
public List<PermissionTicketRepresentation> call() throws Exception {
|
||||
return http.<List<PermissionTicketRepresentation>>get(serverConfiguration.getPermissionEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.param("scopeId", scopeId)
|
||||
.response().json(new TypeReference<List<PermissionTicketRepresentation>>(){}).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error querying permission ticket by scope", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for any permission ticket associated with the given <code>resourceId</code>.
|
||||
*
|
||||
* @param resourceId the resource id (not {@code null})
|
||||
* @return a list of permission tickets associated with the given <code>resourceId</code>
|
||||
*/
|
||||
public List<PermissionTicketRepresentation> findByResource(final String resourceId) {
|
||||
if (resourceId == null) {
|
||||
throw new IllegalArgumentException("Resource id must not be null");
|
||||
}
|
||||
Callable<List<PermissionTicketRepresentation>> callable = new Callable<List<PermissionTicketRepresentation>>() {
|
||||
@Override
|
||||
public List<PermissionTicketRepresentation> call() throws Exception {
|
||||
return http.<List<PermissionTicketRepresentation>>get(serverConfiguration.getPermissionEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.param("resourceId", resourceId)
|
||||
.response().json(new TypeReference<List<PermissionTicketRepresentation>>(){}).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error querying permission ticket by resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for any permission ticket with the matching arguments.
|
||||
*
|
||||
* @param resourceId the resource id or name
|
||||
* @param scopeId the scope id or name
|
||||
* @param owner the owner id or name
|
||||
* @param requester the requester id or name
|
||||
* @param granted if true, only permission tickets marked as granted are returned.
|
||||
* @param returnNames if the response should include names for resource, scope and owner
|
||||
* @param firstResult the position of the first resource to retrieve
|
||||
* @param maxResult the maximum number of resources to retrieve
|
||||
* @return a list of permission tickets with the matching arguments
|
||||
*/
|
||||
public List<PermissionTicketRepresentation> find(final String resourceId,
|
||||
final String scopeId,
|
||||
final String owner,
|
||||
final String requester,
|
||||
final Boolean granted,
|
||||
final Boolean returnNames,
|
||||
final Integer firstResult,
|
||||
final Integer maxResult) {
|
||||
Callable<List<PermissionTicketRepresentation>> callable = new Callable<List<PermissionTicketRepresentation>>() {
|
||||
@Override
|
||||
public List<PermissionTicketRepresentation> call() throws Exception {
|
||||
return http.<List<PermissionTicketRepresentation>>get(serverConfiguration.getPermissionEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.param("resourceId", resourceId)
|
||||
.param("scopeId", scopeId)
|
||||
.param("owner", owner)
|
||||
.param("requester", requester)
|
||||
.param("granted", granted == null ? null : granted.toString())
|
||||
.param("returnNames", returnNames == null ? null : returnNames.toString())
|
||||
.param("firstResult", firstResult == null ? null : firstResult.toString())
|
||||
.param("maxResult", maxResult == null ? null : maxResult.toString())
|
||||
.response().json(new TypeReference<List<PermissionTicketRepresentation>>(){}).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error querying permission ticket", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a permission ticket.
|
||||
*
|
||||
* @param ticket the permission ticket
|
||||
*/
|
||||
public void update(final PermissionTicketRepresentation ticket) {
|
||||
if (ticket == null) {
|
||||
throw new IllegalArgumentException("Permission ticket must not be null or empty");
|
||||
}
|
||||
if (ticket.getId() == null) {
|
||||
throw new IllegalArgumentException("Permission ticket must have an id");
|
||||
}
|
||||
Callable callable = new Callable() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
http.<List>put(serverConfiguration.getPermissionEndpoint())
|
||||
.json(JsonSerialization.writeValueAsBytes(ticket))
|
||||
.authorizationBearer(pat.call())
|
||||
.response().json(List.class).execute();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
try {
|
||||
callable.call();
|
||||
} catch (Exception cause) {
|
||||
Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error updating permission ticket", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,88 +17,214 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.resource;
|
||||
|
||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.authorization.client.util.Throwables;
|
||||
import org.keycloak.authorization.client.util.TokenCallable;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* An entry point for managing resources using the Protection API.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ProtectedResource {
|
||||
|
||||
private final Http http;
|
||||
private final Callable<String> pat;
|
||||
private ServerConfiguration serverConfiguration;
|
||||
private final TokenCallable pat;
|
||||
|
||||
public ProtectedResource(Http http, Callable<String> pat) {
|
||||
ProtectedResource(Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
|
||||
this.http = http;
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
this.pat = pat;
|
||||
}
|
||||
|
||||
public RegistrationResponse create(ResourceRepresentation resource) {
|
||||
try {
|
||||
return this.http.<RegistrationResponse>post("/authz/protection/resource_set")
|
||||
.authorizationBearer(this.pat.call())
|
||||
/**
|
||||
* Creates a new resource.
|
||||
*
|
||||
* @param resource the resource data
|
||||
* @return a {@link RegistrationResponse}
|
||||
*/
|
||||
public ResourceRepresentation create(final ResourceRepresentation resource) {
|
||||
Callable<ResourceRepresentation> callable = new Callable<ResourceRepresentation>() {
|
||||
@Override
|
||||
public ResourceRepresentation call() throws Exception {
|
||||
return http.<ResourceRepresentation>post(serverConfiguration.getResourceRegistrationEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.json(JsonSerialization.writeValueAsBytes(resource))
|
||||
.response().json(RegistrationResponse.class).execute();
|
||||
.response().json(ResourceRepresentation.class).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not create resource", cause);
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Could not create resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ResourceRepresentation resource) {
|
||||
try {
|
||||
this.http.<RegistrationResponse>put("/authz/protection/resource_set/" + resource.getId())
|
||||
.authorizationBearer(this.pat.call())
|
||||
/**
|
||||
* Updates a resource.
|
||||
*
|
||||
* @param resource the resource data
|
||||
* @return a {@link RegistrationResponse}
|
||||
*/
|
||||
public void update(final ResourceRepresentation resource) {
|
||||
if (resource.getId() == null) {
|
||||
throw new IllegalArgumentException("You must provide the resource id");
|
||||
}
|
||||
|
||||
Callable callable = new Callable() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
http.<ResourceRepresentation>put(serverConfiguration.getResourceRegistrationEndpoint() + "/" + resource.getId())
|
||||
.authorizationBearer(pat.call())
|
||||
.json(JsonSerialization.writeValueAsBytes(resource)).execute();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
try {
|
||||
callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not update resource", cause);
|
||||
Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Could not update resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public RegistrationResponse findById(String id) {
|
||||
/**
|
||||
* Query the server for a resource given its <code>id</code>.
|
||||
*
|
||||
* @param id the resource id
|
||||
* @return a {@link ResourceRepresentation}
|
||||
*/
|
||||
public ResourceRepresentation findById(final String id) {
|
||||
Callable<ResourceRepresentation> callable = new Callable<ResourceRepresentation>() {
|
||||
@Override
|
||||
public ResourceRepresentation call() throws Exception {
|
||||
return http.<ResourceRepresentation>get(serverConfiguration.getResourceRegistrationEndpoint() + "/" + id)
|
||||
.authorizationBearer(pat.call())
|
||||
.response().json(ResourceRepresentation.class).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return this.http.<RegistrationResponse>get("/authz/protection/resource_set/" + id)
|
||||
.authorizationBearer(this.pat.call())
|
||||
.response().json(RegistrationResponse.class).execute();
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not find resource", cause);
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Could not find resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> findByFilter(String filter) {
|
||||
/**
|
||||
* Query the server for a resource given its <code>name</code>.
|
||||
*
|
||||
* @param id the resource name
|
||||
* @return a {@link ResourceRepresentation}
|
||||
*/
|
||||
public ResourceRepresentation findByName(String name) {
|
||||
String[] representations = find(null, name, null, null, null, null, null, null);
|
||||
|
||||
if (representations.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findById(representations[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for any resource with the matching arguments.
|
||||
*
|
||||
* @param id the resource id
|
||||
* @param name the resource name
|
||||
* @param uri the resource uri
|
||||
* @param owner the resource owner
|
||||
* @param type the resource type
|
||||
* @param scope the resource scope
|
||||
* @param firstResult the position of the first resource to retrieve
|
||||
* @param maxResult the maximum number of resources to retrieve
|
||||
* @return an array of strings with the resource ids
|
||||
*/
|
||||
public String[] find(final String id, final String name, final String uri, final String owner, final String type, final String scope, final Integer firstResult, final Integer maxResult) {
|
||||
Callable<String[]> callable = new Callable<String[]>() {
|
||||
@Override
|
||||
public String[] call() throws Exception {
|
||||
return http.<String[]>get(serverConfiguration.getResourceRegistrationEndpoint())
|
||||
.authorizationBearer(pat.call())
|
||||
.param("_id", id)
|
||||
.param("name", name)
|
||||
.param("uri", uri)
|
||||
.param("owner", owner)
|
||||
.param("type", type)
|
||||
.param("scope", scope)
|
||||
.param("deep", Boolean.FALSE.toString())
|
||||
.param("first", firstResult != null ? firstResult.toString() : null)
|
||||
.param("max", maxResult != null ? maxResult.toString() : null)
|
||||
.response().json(String[].class).execute();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return this.http.<Set>get("/authz/protection/resource_set")
|
||||
.authorizationBearer(this.pat.call())
|
||||
.param("filter", filter)
|
||||
.response().json(Set.class).execute();
|
||||
return callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not find resource", cause);
|
||||
return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Could not find resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> findAll() {
|
||||
/**
|
||||
* Query the server for all resources.
|
||||
*
|
||||
* @return @return an array of strings with the resource ids
|
||||
*/
|
||||
public String[] findAll() {
|
||||
try {
|
||||
return this.http.<Set>get("/authz/protection/resource_set")
|
||||
.authorizationBearer(this.pat.call())
|
||||
.response().json(Set.class).execute();
|
||||
return find(null,null , null, null, null, null, null, null);
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not find resource", cause);
|
||||
throw Throwables.handleWrapException("Could not find resource", cause);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String id) {
|
||||
try {
|
||||
this.http.delete("/authz/protection/resource_set/" + id)
|
||||
.authorizationBearer(this.pat.call())
|
||||
/**
|
||||
* Deletes a resource with the given <code>id</code>.
|
||||
*
|
||||
* @param id the resource id
|
||||
*/
|
||||
public void delete(final String id) {
|
||||
Callable callable = new Callable() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
http.delete(serverConfiguration.getResourceRegistrationEndpoint() + "/" + id)
|
||||
.authorizationBearer(pat.call())
|
||||
.execute();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
try {
|
||||
callable.call();
|
||||
} catch (Exception cause) {
|
||||
throw handleAndWrapException("Could not delete resource", cause);
|
||||
Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for all resources with the given uri.
|
||||
*
|
||||
* @param uri the resource uri
|
||||
*/
|
||||
public List<ResourceRepresentation> findByUri(String uri) {
|
||||
String[] ids = find(null, null, uri, null, null, null, null, null);
|
||||
|
||||
if (ids.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ResourceRepresentation> representations = new ArrayList<>();
|
||||
|
||||
for (String id : ids) {
|
||||
representations.add(findById(id));
|
||||
}
|
||||
|
||||
return representations;
|
||||
}
|
||||
}
|
|
@ -17,38 +17,58 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.resource;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||
import org.keycloak.authorization.client.util.Http;
|
||||
import org.keycloak.authorization.client.util.TokenCallable;
|
||||
|
||||
/**
|
||||
* An entry point to access the Protection API endpoints.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ProtectionResource {
|
||||
|
||||
private final Callable<String> pat;
|
||||
private final TokenCallable pat;
|
||||
private final Http http;
|
||||
private ServerConfiguration serverConfiguration;
|
||||
|
||||
public ProtectionResource(Http http, Callable<String> pat) {
|
||||
public ProtectionResource(Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
|
||||
if (pat == null) {
|
||||
throw new RuntimeException("No access token was provided when creating client for Protection API.");
|
||||
}
|
||||
|
||||
this.http = http;
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
this.pat = pat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ProtectedResource} which can be used to manage resources.
|
||||
*
|
||||
* @return a {@link ProtectedResource}
|
||||
*/
|
||||
public ProtectedResource resource() {
|
||||
return new ProtectedResource(http, pat);
|
||||
return new ProtectedResource(http, serverConfiguration, pat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PermissionResource} which can be used to manage permission tickets.
|
||||
*
|
||||
* @return a {@link PermissionResource}
|
||||
*/
|
||||
public PermissionResource permission() {
|
||||
return new PermissionResource(http, pat);
|
||||
return new PermissionResource(http, serverConfiguration, pat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspects the given <code>rpt</code> using the token introspection endpoint.
|
||||
*
|
||||
* @param rpt the rpt to introspect
|
||||
* @return the {@link TokenIntrospectionResponse}
|
||||
*/
|
||||
public TokenIntrospectionResponse introspectRequestingPartyToken(String rpt) {
|
||||
return this.http.<TokenIntrospectionResponse>post("/protocol/openid-connect/token/introspect")
|
||||
return this.http.<TokenIntrospectionResponse>post(serverConfiguration.getTokenIntrospectionEndpoint())
|
||||
.authentication()
|
||||
.client()
|
||||
.form()
|
||||
|
|
|
@ -22,8 +22,6 @@ import org.keycloak.authorization.client.ClientAuthenticator;
|
|||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
@ -39,27 +37,19 @@ public class Http {
|
|||
}
|
||||
|
||||
public <R> HttpMethod<R> get(String path) {
|
||||
return method(RequestBuilder.get().setUri(this.serverConfiguration.getIssuer() + path));
|
||||
}
|
||||
|
||||
public <R> HttpMethod<R> get(URI path) {
|
||||
return method(RequestBuilder.get().setUri(path));
|
||||
}
|
||||
|
||||
public <R> HttpMethod<R> post(URI path) {
|
||||
public <R> HttpMethod<R> post(String path) {
|
||||
return method(RequestBuilder.post().setUri(path));
|
||||
}
|
||||
|
||||
public <R> HttpMethod<R> post(String path) {
|
||||
return method(RequestBuilder.post().setUri(this.serverConfiguration.getIssuer() + path));
|
||||
}
|
||||
|
||||
public <R> HttpMethod<R> put(String path) {
|
||||
return method(RequestBuilder.put().setUri(this.serverConfiguration.getIssuer() + path));
|
||||
return method(RequestBuilder.put().setUri(path));
|
||||
}
|
||||
|
||||
public <R> HttpMethod<R> delete(String path) {
|
||||
return method(RequestBuilder.delete().setUri(this.serverConfiguration.getIssuer() + path));
|
||||
return method(RequestBuilder.delete().setUri(path));
|
||||
}
|
||||
|
||||
private <R> HttpMethod<R> method(RequestBuilder builder) {
|
||||
|
|
|
@ -43,17 +43,17 @@ public class HttpMethod<R> {
|
|||
|
||||
private final HttpClient httpClient;
|
||||
private final ClientAuthenticator authenticator;
|
||||
private final RequestBuilder builder;
|
||||
protected final RequestBuilder builder;
|
||||
protected final Configuration configuration;
|
||||
protected final HashMap<String, String> headers;
|
||||
protected final HashMap<String, String> params;
|
||||
protected final Map<String, String> headers;
|
||||
protected final Map<String, List<String>> params;
|
||||
private HttpMethodResponse<R> response;
|
||||
|
||||
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder) {
|
||||
this(configuration, authenticator, builder, new HashMap<String, String>(), new HashMap<String, String>());
|
||||
this(configuration, authenticator, builder, new HashMap<String, List<String>>(), new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
|
||||
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder, Map<String, List<String>> params, Map<String, String> headers) {
|
||||
this.configuration = configuration;
|
||||
this.httpClient = configuration.getHttpClient();
|
||||
this.authenticator = authenticator;
|
||||
|
@ -108,8 +108,10 @@ public class HttpMethod<R> {
|
|||
}
|
||||
|
||||
protected void preExecute(RequestBuilder builder) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
builder.addParameter(param.getKey(), param.getValue());
|
||||
for (Map.Entry<String, List<String>> param : params.entrySet()) {
|
||||
for (String value : param.getValue()) {
|
||||
builder.addParameter(param.getKey(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +130,30 @@ public class HttpMethod<R> {
|
|||
}
|
||||
|
||||
public HttpMethod<R> param(String name, String value) {
|
||||
this.params.put(name, value);
|
||||
if (value != null) {
|
||||
List<String> values = params.get(name);
|
||||
|
||||
if (values == null || !values.isEmpty()) {
|
||||
values = new ArrayList<>();
|
||||
params.put(name, values);
|
||||
}
|
||||
|
||||
values.add(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMethod<R> params(String name, String value) {
|
||||
if (value != null) {
|
||||
List<String> values = params.get(name);
|
||||
|
||||
if (values == null) {
|
||||
values = new ArrayList<>();
|
||||
params.put(name, values);
|
||||
}
|
||||
|
||||
values.add(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -145,8 +170,10 @@ public class HttpMethod<R> {
|
|||
if (params != null) {
|
||||
List<NameValuePair> formparams = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
|
||||
for (Map.Entry<String, List<String>> param : params.entrySet()) {
|
||||
for (String value : param.getValue()) {
|
||||
formparams.add(new BasicNameValuePair(param.getKey(), value));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -17,8 +17,16 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
||||
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
|
||||
import org.keycloak.representations.idm.authorization.PermissionTicketToken.ResourcePermission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -34,16 +42,84 @@ public class HttpMethodAuthenticator<R> {
|
|||
}
|
||||
|
||||
public HttpMethod<R> client() {
|
||||
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||
this.method.params.put(OAuth2Constants.GRANT_TYPE, Arrays.asList(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||
authenticator.configureClientCredentials(this.method.params, this.method.headers);
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public HttpMethod<R> oauth2ResourceOwnerPassword(String userName, String password) {
|
||||
client();
|
||||
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
this.method.params.put("username", userName);
|
||||
this.method.params.put("password", password);
|
||||
this.method.params.put(OAuth2Constants.GRANT_TYPE, Arrays.asList(OAuth2Constants.PASSWORD));
|
||||
this.method.params.put("username", Arrays.asList(userName));
|
||||
this.method.params.put("password", Arrays.asList(password));
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public HttpMethod<R> uma() {
|
||||
// if there is an authorization bearer header authenticate using bearer token
|
||||
Header authorizationHeader = method.builder.getFirstHeader("Authorization");
|
||||
|
||||
if (!(authorizationHeader != null && authorizationHeader.getValue().toLowerCase().startsWith("bearer"))) {
|
||||
client();
|
||||
}
|
||||
|
||||
method.params.put(OAuth2Constants.GRANT_TYPE, Arrays.asList(OAuth2Constants.UMA_GRANT_TYPE));
|
||||
return method;
|
||||
}
|
||||
|
||||
public HttpMethod<R> uma(AuthorizationRequest request) {
|
||||
String ticket = request.getTicket();
|
||||
PermissionTicketToken permissions = request.getPermissions();
|
||||
|
||||
if (ticket == null && permissions == null) {
|
||||
throw new IllegalArgumentException("You must either provide a permission ticket or the permissions you want to request.");
|
||||
}
|
||||
|
||||
uma();
|
||||
method.param("ticket", ticket);
|
||||
method.param("claim_token", request.getClaimToken());
|
||||
method.param("claim_token_format", request.getClaimTokenFormat());
|
||||
method.param("pct", request.getPct());
|
||||
method.param("rpt", request.getRpt());
|
||||
method.param("scope", request.getScope());
|
||||
method.param("audience", request.getAudience());
|
||||
|
||||
if (permissions != null) {
|
||||
for (ResourcePermission permission : permissions.getResources()) {
|
||||
String resourceId = permission.getResourceId();
|
||||
Set<String> scopes = permission.getScopes();
|
||||
StringBuilder value = new StringBuilder();
|
||||
|
||||
if (resourceId != null) {
|
||||
value.append(resourceId);
|
||||
}
|
||||
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
value.append("#");
|
||||
for (String scope : scopes) {
|
||||
if (!value.toString().endsWith("#")) {
|
||||
value.append(",");
|
||||
}
|
||||
value.append(scope);
|
||||
}
|
||||
}
|
||||
|
||||
method.params("permission", value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Metadata metadata = request.getMetadata();
|
||||
|
||||
if (metadata != null) {
|
||||
if (metadata.getIncludeResourceName() != null) {
|
||||
method.param("response_include_resource_name", metadata.getIncludeResourceName().toString());
|
||||
}
|
||||
|
||||
if (metadata.getLimit() != null) {
|
||||
method.param("response_permissions_limit", metadata.getLimit().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.util;
|
||||
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
@ -58,4 +60,22 @@ public class HttpMethodResponse<R> {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public HttpMethodResponse<R> json(final TypeReference responseType) {
|
||||
return new HttpMethodResponse<R>(this.method) {
|
||||
@Override
|
||||
public R execute() {
|
||||
return method.execute(new HttpResponseProcessor<R>() {
|
||||
@Override
|
||||
public R process(byte[] entity) {
|
||||
try {
|
||||
return (R) JsonSerialization.readValue(new ByteArrayInputStream(entity), responseType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error parsing JSON response.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@
|
|||
*/
|
||||
package org.keycloak.authorization.client.util;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -24,19 +27,68 @@ import org.keycloak.authorization.client.AuthorizationDeniedException;
|
|||
public final class Throwables {
|
||||
|
||||
/**
|
||||
* Handles an {@code exception} and wraps it into a {@link RuntimeException}. The resulting exception contains
|
||||
* more details in case the given {@code exception} is of a {@link HttpResponseException}.
|
||||
* Handles an {@code cause} and wraps it into a {@link RuntimeException}. The resulting cause contains
|
||||
* more details in case the given {@code cause} is of a {@link HttpResponseException}.
|
||||
*
|
||||
*
|
||||
* @param callable
|
||||
* @param pat
|
||||
* @param message the message
|
||||
* @param exception the root exception
|
||||
* @return a {@link RuntimeException} wrapping the given {@code exception}
|
||||
* @param cause the root cause
|
||||
* @return a {@link RuntimeException} wrapping the given {@code cause}
|
||||
*/
|
||||
public static RuntimeException handleAndWrapException(String message, Exception exception) {
|
||||
if (exception instanceof HttpResponseException) {
|
||||
throw handleAndWrapHttpResponseException(message, HttpResponseException.class.cast(exception));
|
||||
public static RuntimeException handleWrapException(String message, Throwable cause) {
|
||||
if (cause instanceof HttpResponseException) {
|
||||
throw handleAndWrapHttpResponseException(message, HttpResponseException.class.cast(cause));
|
||||
}
|
||||
|
||||
return new RuntimeException(message, exception);
|
||||
return new RuntimeException(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Retries the given {@code callable} after obtaining a fresh {@code token} from the server. If the attempt to retry fails
|
||||
* the exception is handled as defined by {@link #handleWrapException(String, Throwable)}.
|
||||
*
|
||||
* <p>A retry is only attempted in case the {@code cause} is a {@link HttpResponseException} with a 403 status code. In some cases the
|
||||
* session associated with the token is no longer valid and a new token must be issues.
|
||||
*
|
||||
* @param callable the callable to retry
|
||||
* @param token the token
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
* @param <V> the result of the callable
|
||||
* @return the result of the callable
|
||||
* @throws RuntimeException in case the attempt to retry fails
|
||||
*/
|
||||
public static <V> V retryAndWrapExceptionIfNecessary(Callable<V> callable, TokenCallable token, String message, Throwable cause) throws RuntimeException {
|
||||
if (token == null || !token.isRetry()) {
|
||||
throw handleWrapException(message, cause);
|
||||
}
|
||||
|
||||
if (cause instanceof HttpResponseException) {
|
||||
HttpResponseException httpe = HttpResponseException.class.cast(cause);
|
||||
|
||||
if (httpe.getStatusCode() == 403) {
|
||||
TokenIntrospectionResponse response = token.getHttp().<TokenIntrospectionResponse>post(token.getServerConfiguration().getTokenIntrospectionEndpoint())
|
||||
.authentication()
|
||||
.client()
|
||||
.param("token", token.call())
|
||||
.response().json(TokenIntrospectionResponse.class).execute();
|
||||
|
||||
if (!response.getActive()) {
|
||||
token.clearToken();
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw handleWrapException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
throw handleWrapException(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(message, cause);
|
||||
}
|
||||
|
||||
private static RuntimeException handleAndWrapHttpResponseException(String message, HttpResponseException exception) {
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2018 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.util;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
public class TokenCallable implements Callable<String> {
|
||||
|
||||
private final String userName;
|
||||
private final String password;
|
||||
private final Http http;
|
||||
private final Configuration configuration;
|
||||
private final ServerConfiguration serverConfiguration;
|
||||
private AccessTokenResponse clientToken;
|
||||
|
||||
public TokenCallable(String userName, String password, Http http, Configuration configuration, ServerConfiguration serverConfiguration) {
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
this.http = http;
|
||||
this.configuration = configuration;
|
||||
this.serverConfiguration = serverConfiguration;
|
||||
}
|
||||
|
||||
public TokenCallable(Http http, Configuration configuration, ServerConfiguration serverConfiguration) {
|
||||
this(null, null, http, configuration, serverConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() {
|
||||
if (clientToken == null) {
|
||||
if (userName == null || password == null) {
|
||||
clientToken = obtainAccessToken();
|
||||
} else {
|
||||
clientToken = obtainAccessToken(userName, password);
|
||||
}
|
||||
}
|
||||
|
||||
String token = clientToken.getToken();
|
||||
|
||||
try {
|
||||
AccessToken accessToken = JsonSerialization.readValue(new JWSInput(token).getContent(), AccessToken.class);
|
||||
|
||||
if (accessToken.isActive()) {
|
||||
return token;
|
||||
}
|
||||
|
||||
clientToken = http.<AccessTokenResponse>post(serverConfiguration.getTokenEndpoint())
|
||||
.authentication().client()
|
||||
.form()
|
||||
.param("grant_type", "refresh_token")
|
||||
.param("refresh_token", clientToken.getRefreshToken())
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
} catch (Exception e) {
|
||||
clientToken = null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return clientToken.getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an access token using the client credentials.
|
||||
*
|
||||
* @return an {@link AccessTokenResponse}
|
||||
*/
|
||||
AccessTokenResponse obtainAccessToken() {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.client()
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an access token using the resource owner credentials.
|
||||
*
|
||||
* @return an {@link AccessTokenResponse}
|
||||
*/
|
||||
AccessTokenResponse obtainAccessToken(String userName, String password) {
|
||||
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
|
||||
.authentication()
|
||||
.oauth2ResourceOwnerPassword(userName, password)
|
||||
.response()
|
||||
.json(AccessTokenResponse.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
Http getHttp() {
|
||||
return http;
|
||||
}
|
||||
|
||||
protected boolean isRetry() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Configuration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
ServerConfiguration getServerConfiguration() {
|
||||
return serverConfiguration;
|
||||
}
|
||||
|
||||
void clearToken() {
|
||||
clientToken = null;
|
||||
}
|
||||
}
|
|
@ -51,6 +51,15 @@ public class Time {
|
|||
return new Date(((long) time ) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Date} object, its value set to time
|
||||
* @param time Time in milliseconds since the epoch
|
||||
* @return see description
|
||||
*/
|
||||
public static Date toDate(long time) {
|
||||
return new Date(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time in milliseconds for a time in seconds. No adjustment is made to the parameter.
|
||||
* @param time Time in seconds since the epoch
|
||||
|
|
|
@ -68,7 +68,7 @@ public class AuthorizationContext {
|
|||
if (hasResourcePermission(resourceName)) {
|
||||
for (Permission permission : authorization.getPermissions()) {
|
||||
for (PathConfig pathHolder : paths.values()) {
|
||||
if (pathHolder.getId().equals(permission.getResourceSetId())) {
|
||||
if (pathHolder.getId().equals(permission.getResourceId())) {
|
||||
if (permission.getScopes().contains(scopeName)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ public class AuthorizationContext {
|
|||
}
|
||||
|
||||
for (Permission permission : authorization.getPermissions()) {
|
||||
if (permission.getResourceSetName().equals(resourceName) || permission.getResourceSetId().equals(resourceName)) {
|
||||
if (permission.getResourceName().equals(resourceName) || permission.getResourceId().equals(resourceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,8 @@ public interface OAuth2Constants {
|
|||
String JWT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt";
|
||||
String ID_TOKEN_TYPE="urn:ietf:params:oauth:token-type:id_token";
|
||||
|
||||
String UMA_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
*/
|
||||
package org.keycloak.representations.adapters.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
@ -37,26 +36,18 @@ public class PolicyEnforcerConfig {
|
|||
@JsonProperty("enforcement-mode")
|
||||
private EnforcementMode enforcementMode = EnforcementMode.ENFORCING;
|
||||
|
||||
@JsonProperty("user-managed-access")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private UmaProtocolConfig userManagedAccess;
|
||||
|
||||
@JsonProperty("entitlement")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private EntitlementProtocolConfig entitlement;
|
||||
|
||||
@JsonProperty("paths")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private List<PathConfig> paths = new ArrayList<>();
|
||||
|
||||
@JsonProperty("online-introspection")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Boolean onlineIntrospection = Boolean.FALSE;
|
||||
|
||||
@JsonProperty("on-deny-redirect-to")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String onDenyRedirectTo;
|
||||
|
||||
@JsonProperty("user-managed-access")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private UserManagedAccessConfig userManagedAccess;
|
||||
|
||||
public Boolean isCreateResources() {
|
||||
return this.createResources;
|
||||
}
|
||||
|
@ -73,26 +64,14 @@ public class PolicyEnforcerConfig {
|
|||
this.enforcementMode = enforcementMode;
|
||||
}
|
||||
|
||||
public UmaProtocolConfig getUserManagedAccess() {
|
||||
public UserManagedAccessConfig getUserManagedAccess() {
|
||||
return this.userManagedAccess;
|
||||
}
|
||||
|
||||
public EntitlementProtocolConfig getEntitlement() {
|
||||
return this.entitlement;
|
||||
}
|
||||
|
||||
public Boolean isOnlineIntrospection() {
|
||||
return onlineIntrospection;
|
||||
}
|
||||
|
||||
public void setCreateResources(Boolean createResources) {
|
||||
this.createResources = createResources;
|
||||
}
|
||||
|
||||
public void setOnlineIntrospection(Boolean onlineIntrospection) {
|
||||
this.onlineIntrospection = onlineIntrospection;
|
||||
}
|
||||
|
||||
public void setPaths(List<PathConfig> paths) {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
@ -101,14 +80,10 @@ public class PolicyEnforcerConfig {
|
|||
return onDenyRedirectTo;
|
||||
}
|
||||
|
||||
public void setUserManagedAccess(UmaProtocolConfig userManagedAccess) {
|
||||
public void setUserManagedAccess(UserManagedAccessConfig userManagedAccess) {
|
||||
this.userManagedAccess = userManagedAccess;
|
||||
}
|
||||
|
||||
public void setEntitlement(EntitlementProtocolConfig entitlement) {
|
||||
this.entitlement = entitlement;
|
||||
}
|
||||
|
||||
public void setOnDenyRedirectTo(String onDenyRedirectTo) {
|
||||
this.onDenyRedirectTo = onDenyRedirectTo;
|
||||
}
|
||||
|
@ -259,11 +234,7 @@ public class PolicyEnforcerConfig {
|
|||
ANY
|
||||
}
|
||||
|
||||
public static class UmaProtocolConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class EntitlementProtocolConfig {
|
||||
public static class UserManagedAccessConfig {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,6 +145,8 @@ public class RealmRepresentation {
|
|||
|
||||
protected String keycloakVersion;
|
||||
|
||||
protected Boolean userManagedAccessAllowed;
|
||||
|
||||
@Deprecated
|
||||
protected Boolean social;
|
||||
@Deprecated
|
||||
|
@ -964,4 +966,12 @@ public class RealmRepresentation {
|
|||
public void setFederatedUsers(List<UserRepresentation> federatedUsers) {
|
||||
this.federatedUsers = federatedUsers;
|
||||
}
|
||||
|
||||
public void setUserManagedAccessAllowed(Boolean userManagedAccessAllowed) {
|
||||
this.userManagedAccessAllowed = userManagedAccessAllowed;
|
||||
}
|
||||
|
||||
public Boolean isUserManagedAccessAllowed() {
|
||||
return userManagedAccessAllowed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Copyright 2018 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.representations.idm.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.representations.idm.authorization.PermissionTicketToken.ResourcePermission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationRequest {
|
||||
|
||||
private String ticket;
|
||||
private String rpt;
|
||||
private String claimToken;
|
||||
private String claimTokenFormat;
|
||||
private String pct;
|
||||
private String scope;
|
||||
private PermissionTicketToken permissions = new PermissionTicketToken();
|
||||
private Metadata metadata;
|
||||
private String audience;
|
||||
private String accessToken;
|
||||
private boolean submitRequest;
|
||||
|
||||
public AuthorizationRequest(String ticket) {
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
public AuthorizationRequest() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return this.ticket;
|
||||
}
|
||||
|
||||
public void setTicket(String ticket) {
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
public String getRpt() {
|
||||
return this.rpt;
|
||||
}
|
||||
|
||||
public void setRpt(String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
|
||||
public void setClaimToken(String claimToken) {
|
||||
this.claimToken = claimToken;
|
||||
}
|
||||
|
||||
public String getClaimToken() {
|
||||
return claimToken;
|
||||
}
|
||||
|
||||
public void setClaimTokenFormat(String claimTokenFormat) {
|
||||
this.claimTokenFormat = claimTokenFormat;
|
||||
}
|
||||
|
||||
public String getClaimTokenFormat() {
|
||||
return claimTokenFormat;
|
||||
}
|
||||
|
||||
public void setPct(String pct) {
|
||||
this.pct = pct;
|
||||
}
|
||||
|
||||
public String getPct() {
|
||||
return pct;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setPermissions(PermissionTicketToken permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public PermissionTicketToken getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Metadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public void setAudience(String audience) {
|
||||
this.audience = audience;
|
||||
}
|
||||
|
||||
public String getAudience() {
|
||||
return audience;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void addPermission(String resourceId, List<String> scopes) {
|
||||
addPermission(resourceId, scopes.toArray(new String[scopes.size()]));
|
||||
}
|
||||
|
||||
public void addPermission(String resourceId, String... scopes) {
|
||||
if (permissions == null) {
|
||||
permissions = new PermissionTicketToken(new ArrayList<ResourcePermission>());
|
||||
}
|
||||
|
||||
ResourcePermission permission = null;
|
||||
|
||||
for (ResourcePermission resourcePermission : permissions.getResources()) {
|
||||
if (resourcePermission.getResourceId().equals(resourceId)) {
|
||||
permission = resourcePermission;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == null) {
|
||||
permission = new ResourcePermission(resourceId, new HashSet<String>());
|
||||
permissions.getResources().add(permission);
|
||||
}
|
||||
|
||||
permission.getScopes().addAll(Arrays.asList(scopes));
|
||||
}
|
||||
|
||||
public void setSubmitRequest(boolean submitRequest) {
|
||||
this.submitRequest = submitRequest;
|
||||
}
|
||||
|
||||
public boolean isSubmitRequest() {
|
||||
return submitRequest && ticket != null;
|
||||
}
|
||||
|
||||
public static class Metadata {
|
||||
|
||||
private Boolean includeResourceName;
|
||||
private Integer limit;
|
||||
|
||||
public Boolean getIncludeResourceName() {
|
||||
if (includeResourceName == null) {
|
||||
includeResourceName = Boolean.TRUE;
|
||||
}
|
||||
return includeResourceName;
|
||||
}
|
||||
|
||||
public void setIncludeResourceName(Boolean includeResourceName) {
|
||||
this.includeResourceName = includeResourceName;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2018 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.representations.idm.authorization;
|
||||
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationResponse extends AccessTokenResponse {
|
||||
|
||||
private boolean upgraded;
|
||||
|
||||
public AuthorizationResponse() {
|
||||
}
|
||||
|
||||
public AuthorizationResponse(AccessTokenResponse response, boolean upgraded) {
|
||||
setToken(response.getToken());
|
||||
setTokenType("Bearer");
|
||||
setRefreshToken(response.getRefreshToken());
|
||||
setRefreshExpiresIn(response.getRefreshExpiresIn());
|
||||
setExpiresIn(response.getExpiresIn());
|
||||
setNotBeforePolicy(response.getNotBeforePolicy());
|
||||
this.upgraded = upgraded;
|
||||
}
|
||||
|
||||
public boolean isUpgraded() {
|
||||
return upgraded;
|
||||
}
|
||||
|
||||
public void setUpgraded(boolean upgraded) {
|
||||
this.upgraded = upgraded;
|
||||
}
|
||||
}
|
|
@ -28,11 +28,11 @@ import java.util.Set;
|
|||
*/
|
||||
public class Permission {
|
||||
|
||||
@JsonProperty("resource_set_id")
|
||||
private String resourceSetId;
|
||||
@JsonProperty("rsid")
|
||||
private String resourceId;
|
||||
|
||||
@JsonProperty("resource_set_name")
|
||||
private final String resourceSetName;
|
||||
@JsonProperty("rsname")
|
||||
private final String resourceName;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Set<String> scopes;
|
||||
|
@ -44,19 +44,19 @@ public class Permission {
|
|||
this(null, null, null, null);
|
||||
}
|
||||
|
||||
public Permission(final String resourceSetId, String resourceSetName, final Set<String> scopes, Map<String, Set<String>> claims) {
|
||||
this.resourceSetId = resourceSetId;
|
||||
this.resourceSetName = resourceSetName;
|
||||
public Permission(final String resourceId, String resourceName, final Set<String> scopes, Map<String, Set<String>> claims) {
|
||||
this.resourceId = resourceId;
|
||||
this.resourceName = resourceName;
|
||||
this.scopes = scopes;
|
||||
this.claims = claims;
|
||||
}
|
||||
|
||||
public String getResourceSetId() {
|
||||
return this.resourceSetId;
|
||||
public String getResourceId() {
|
||||
return this.resourceId;
|
||||
}
|
||||
|
||||
public String getResourceSetName() {
|
||||
return this.resourceSetName;
|
||||
public String getResourceName() {
|
||||
return this.resourceName;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
|
@ -75,7 +75,7 @@ public class Permission {
|
|||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("Permission {").append("id=").append(resourceSetId).append(", name=").append(resourceSetName)
|
||||
builder.append("Permission {").append("id=").append(resourceId).append(", name=").append(resourceName)
|
||||
.append(", scopes=").append(scopes).append("}");
|
||||
|
||||
return builder.toString();
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2018 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.representations.idm.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionRequest {
|
||||
|
||||
private String resourceId;
|
||||
private Set<String> scopes;
|
||||
private String resourceServerId;
|
||||
|
||||
public PermissionRequest(String resourceId, String... scopes) {
|
||||
this.resourceId = resourceId;
|
||||
if (scopes != null) {
|
||||
this.scopes = new HashSet(Arrays.asList(scopes));
|
||||
}
|
||||
}
|
||||
|
||||
public PermissionRequest() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@JsonProperty("resource_id")
|
||||
public void setResourceId(String resourceSetId) {
|
||||
this.resourceId = resourceSetId;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@JsonProperty("resource_scopes")
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
@JsonProperty("resource_server_id")
|
||||
public void setResourceServerId(String resourceServerId) {
|
||||
this.resourceServerId = resourceServerId;
|
||||
}
|
||||
|
||||
public String getResourceServerId() {
|
||||
return resourceServerId;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
* Copyright 2018 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.
|
||||
|
@ -15,7 +14,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
|
||||
package org.keycloak.representations.idm.authorization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2017 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.representations.idm.authorization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionTicketRepresentation {
|
||||
|
||||
private String id;
|
||||
private String owner;
|
||||
private String resource;
|
||||
private String scope;
|
||||
private boolean granted;
|
||||
private String scopeName;
|
||||
private String resourceName;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
|
||||
public void setGranted(boolean granted) {
|
||||
this.granted = granted;
|
||||
}
|
||||
|
||||
public void setScopeName(String scopeName) {
|
||||
this.scopeName = scopeName;
|
||||
}
|
||||
|
||||
public String getScopeName() {
|
||||
return scopeName;
|
||||
}
|
||||
|
||||
public void setResourceName(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
return resourceName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2017 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.representations.idm.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.TokenIdGenerator;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionTicketToken extends JsonWebToken {
|
||||
|
||||
private final List<ResourcePermission> resources;
|
||||
|
||||
public PermissionTicketToken() {
|
||||
this(new ArrayList<ResourcePermission>());
|
||||
}
|
||||
|
||||
public PermissionTicketToken(List<ResourcePermission> resources, String audience, AccessToken accessToken) {
|
||||
if (accessToken != null) {
|
||||
id(TokenIdGenerator.generateId());
|
||||
subject(accessToken.getSubject());
|
||||
expiration(accessToken.getExpiration());
|
||||
notBefore(accessToken.getNotBefore());
|
||||
issuedAt(accessToken.getIssuedAt());
|
||||
issuedFor(accessToken.getIssuedFor());
|
||||
}
|
||||
if (audience != null) {
|
||||
audience(audience);
|
||||
}
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public PermissionTicketToken(List<ResourcePermission> resources) {
|
||||
this(resources, null, null);
|
||||
}
|
||||
|
||||
public List<ResourcePermission> getResources() {
|
||||
return this.resources;
|
||||
}
|
||||
|
||||
public static class ResourcePermission {
|
||||
|
||||
@JsonProperty("id")
|
||||
private String resourceId;
|
||||
|
||||
@JsonProperty("scopes")
|
||||
private Set<String> scopes;
|
||||
|
||||
public ResourcePermission() {
|
||||
}
|
||||
|
||||
public ResourcePermission(String resourceId, Set<String> scopes) {
|
||||
this.resourceId = resourceId;
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,14 @@ public class ResourceOwnerRepresentation {
|
|||
private String id;
|
||||
private String name;
|
||||
|
||||
public ResourceOwnerRepresentation() {
|
||||
|
||||
}
|
||||
|
||||
public ResourceOwnerRepresentation(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
|
|
@ -47,10 +47,12 @@ public class ResourceRepresentation {
|
|||
@JsonProperty("icon_uri")
|
||||
private String iconUri;
|
||||
private ResourceOwnerRepresentation owner;
|
||||
private Boolean ownerManagedAccess;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private List<PolicyRepresentation> policies;
|
||||
private List<ScopeRepresentation> typedScopes;
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
|
@ -121,6 +123,10 @@ public class ResourceRepresentation {
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
@ -145,6 +151,10 @@ public class ResourceRepresentation {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setUri(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
@ -169,6 +179,14 @@ public class ResourceRepresentation {
|
|||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Boolean getOwnerManagedAccess() {
|
||||
return ownerManagedAccess;
|
||||
}
|
||||
|
||||
public void setOwnerManagedAccess(Boolean ownerManagedAccess) {
|
||||
this.ownerManagedAccess = ownerManagedAccess;
|
||||
}
|
||||
|
||||
public void setTypedScopes(List<ScopeRepresentation> typedScopes) {
|
||||
this.typedScopes = typedScopes;
|
||||
}
|
||||
|
@ -177,6 +195,15 @@ public class ResourceRepresentation {
|
|||
return typedScopes;
|
||||
}
|
||||
|
||||
public void addScope(String... scopeNames) {
|
||||
if (scopes == null) {
|
||||
scopes = new HashSet<>();
|
||||
}
|
||||
for (String scopeName : scopeNames) {
|
||||
scopes.add(new ScopeRepresentation(scopeName));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
|
|
@ -35,6 +35,7 @@ public class ScopeRepresentation {
|
|||
private String iconUri;
|
||||
private List<PolicyRepresentation> policies;
|
||||
private List<ResourceRepresentation> resources;
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
|
@ -67,6 +68,10 @@ public class ScopeRepresentation {
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getIconUri() {
|
||||
return this.iconUri;
|
||||
}
|
||||
|
@ -83,6 +88,10 @@ public class ScopeRepresentation {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setIconUri(String iconUri) {
|
||||
this.iconUri = iconUri;
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
for (Permission permission : authzContext.getPermissions()) {
|
||||
%>
|
||||
<li>
|
||||
<p>Resource: <%= permission.getResourceSetName() %></p>
|
||||
<p>ID: <%= permission.getResourceSetId() %></p>
|
||||
<p>Resource: <%= permission.getResourceName() %></p>
|
||||
<p>ID: <%= permission.getResourceId() %></p>
|
||||
</li>
|
||||
<%
|
||||
}
|
||||
|
|
|
@ -18,18 +18,14 @@
|
|||
package org.keycloak.authz.helloworld;
|
||||
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
|
@ -47,28 +43,10 @@ public class AuthorizationClientExample {
|
|||
// create a new instance based on the configuration defined in keycloak-authz.json
|
||||
AuthzClient authzClient = AuthzClient.create();
|
||||
|
||||
// query the server for a resource with a given name
|
||||
Set<String> resourceId = authzClient.protection()
|
||||
.resource()
|
||||
.findByFilter("name=Default Resource");
|
||||
|
||||
// obtain an Entitlement API Token in order to get access to the Entitlement API.
|
||||
// this token is just an access token issued to a client on behalf of an user
|
||||
// with a scope = kc_entitlement
|
||||
String eat = getEntitlementAPIToken(authzClient);
|
||||
|
||||
// create an entitlement request
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permission = new PermissionRequest();
|
||||
|
||||
permission.setResourceSetId(resourceId.iterator().next());
|
||||
|
||||
request.addPermission(permission);
|
||||
|
||||
// send the entitlement request to the server in order to
|
||||
// send the authorization request to the server in order to
|
||||
// obtain an RPT with all permissions granted to the user
|
||||
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
||||
String rpt = response.getRpt();
|
||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize();
|
||||
String rpt = response.getToken();
|
||||
|
||||
TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);
|
||||
|
||||
|
@ -78,7 +56,6 @@ public class AuthorizationClientExample {
|
|||
for (Permission granted : requestingPartyToken.getPermissions()) {
|
||||
System.out.println(granted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void createResource() {
|
||||
|
@ -94,18 +71,18 @@ public class AuthorizationClientExample {
|
|||
newResource.addScope(new ScopeRepresentation("urn:hello-world-authz:scopes:view"));
|
||||
|
||||
ProtectedResource resourceClient = authzClient.protection().resource();
|
||||
Set<String> existingResource = resourceClient.findByFilter("name=" + newResource.getName());
|
||||
ResourceRepresentation existingResource = resourceClient.findByName(newResource.getName());
|
||||
|
||||
if (!existingResource.isEmpty()) {
|
||||
resourceClient.delete(existingResource.iterator().next());
|
||||
if (existingResource != null) {
|
||||
resourceClient.delete(existingResource.getId());
|
||||
}
|
||||
|
||||
// create the resource on the server
|
||||
RegistrationResponse response = resourceClient.create(newResource);
|
||||
ResourceRepresentation response = resourceClient.create(newResource);
|
||||
String resourceId = response.getId();
|
||||
|
||||
// query the resource using its newly generated id
|
||||
ResourceRepresentation resource = resourceClient.findById(resourceId).getResourceDescription();
|
||||
ResourceRepresentation resource = resourceClient.findById(resourceId);
|
||||
|
||||
System.out.println(resource);
|
||||
}
|
||||
|
@ -120,20 +97,20 @@ public class AuthorizationClientExample {
|
|||
resource.setName("New Resource");
|
||||
|
||||
ProtectedResource resourceClient = authzClient.protection().resource();
|
||||
Set<String> existingResource = resourceClient.findByFilter("name=" + resource.getName());
|
||||
ResourceRepresentation existingResource = resourceClient.findByName(resource.getName());
|
||||
|
||||
if (existingResource.isEmpty()) {
|
||||
if (existingResource == null) {
|
||||
createResource();
|
||||
}
|
||||
|
||||
resource.setId(existingResource.iterator().next());
|
||||
resource.setId(existingResource.getId());
|
||||
resource.setUri("Changed URI");
|
||||
|
||||
// update the resource on the server
|
||||
resourceClient.update(resource);
|
||||
|
||||
// query the resource using its newly generated id
|
||||
ResourceRepresentation existing = resourceClient.findById(resource.getId()).getResourceDescription();
|
||||
ResourceRepresentation existing = resourceClient.findById(resource.getId());
|
||||
|
||||
System.out.println(existing);
|
||||
}
|
||||
|
@ -142,23 +119,16 @@ public class AuthorizationClientExample {
|
|||
// create a new instance based on the configuration define at keycloak-authz.json
|
||||
AuthzClient authzClient = AuthzClient.create();
|
||||
|
||||
// obtain an Entitlement API Token in order to get access to the Entitlement API.
|
||||
// this token is just an access token issued to a client on behalf of an user
|
||||
// with a scope = kc_entitlement
|
||||
String eat = getEntitlementAPIToken(authzClient);
|
||||
// create an authorization request
|
||||
AuthorizationRequest request = new AuthorizationRequest();
|
||||
|
||||
// create an entitlement request
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permission = new PermissionRequest();
|
||||
// add permissions to the request based on the resources and scopes you want to check access
|
||||
request.addPermission("Default Resource");
|
||||
|
||||
permission.setResourceSetName("Default Resource");
|
||||
|
||||
request.addPermission(permission);
|
||||
|
||||
// send the entitlement request to the server in order to obtain a RPT
|
||||
// with all permissions granted to the user
|
||||
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
||||
String rpt = response.getRpt();
|
||||
// send the entitlement request to the server in order to
|
||||
// obtain an RPT with permissions for a single resource
|
||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);
|
||||
String rpt = response.getToken();
|
||||
|
||||
System.out.println("You got a RPT: " + rpt);
|
||||
|
||||
|
@ -169,27 +139,16 @@ public class AuthorizationClientExample {
|
|||
// create a new instance based on the configuration defined in keycloak-authz.json
|
||||
AuthzClient authzClient = AuthzClient.create();
|
||||
|
||||
// obtian a Entitlement API Token in order to get access to the Entitlement API.
|
||||
// this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
|
||||
String eat = getEntitlementAPIToken(authzClient);
|
||||
// create an authorization request
|
||||
AuthorizationRequest request = new AuthorizationRequest();
|
||||
|
||||
// send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user
|
||||
EntitlementResponse response = authzClient.entitlement(eat).getAll("hello-world-authz-service");
|
||||
String rpt = response.getRpt();
|
||||
// send the entitlement request to the server in order to
|
||||
// obtain an RPT with all permissions granted to the user
|
||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);
|
||||
String rpt = response.getToken();
|
||||
|
||||
System.out.println("You got a RPT: " + rpt);
|
||||
|
||||
// now you can use the RPT to access protected resources on the resource server
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an Entitlement API Token or EAT from the server. Usually, EATs are going to be obtained by clients using a
|
||||
* authorization_code grant type. Here we are using resource owner credentials for demonstration purposes.
|
||||
*
|
||||
* @param authzClient the authorization client instance
|
||||
* @return a string representing a EAT
|
||||
*/
|
||||
private static String getEntitlementAPIToken(AuthzClient authzClient) {
|
||||
return authzClient.obtainAccessToken("alice", "alice").getToken();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"realm": "hello-world-authz",
|
||||
"auth-server-url" : "http://localhost:8080/auth",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"resource" : "hello-world-authz-service",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
<script src="lib/angular/angular-route.min.js"></script>
|
||||
<script src="lib/jwt-decode.min.js"></script>
|
||||
|
||||
<script src="http://localhost:8080/auth/js/keycloak.js"></script>
|
||||
<script src="http://localhost:8080/auth/js/keycloak-authz.js"></script>
|
||||
<script src="http://localhost:8180/auth/js/keycloak.js"></script>
|
||||
<script src="http://localhost:8180/auth/js/keycloak-authz.js"></script>
|
||||
<script src="js/identity.js" type="text/javascript"></script>
|
||||
<script src="js/app.js" type="text/javascript"></script>
|
||||
</head>
|
||||
|
||||
<body data-ng-controller="TokenCtrl">
|
||||
|
||||
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
|
||||
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.account()">My Account</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
|
||||
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<div id="content" ng-view/>
|
||||
|
|
|
@ -42,6 +42,9 @@ module.controller('GlobalCtrl', function ($scope, $http, $route, $location, Albu
|
|||
Album.query(function (albums) {
|
||||
$scope.albums = albums;
|
||||
});
|
||||
Album.shares(function (albums) {
|
||||
$scope.shares = albums;
|
||||
});
|
||||
|
||||
$scope.Identity = Identity;
|
||||
|
||||
|
@ -50,6 +53,23 @@ module.controller('GlobalCtrl', function ($scope, $http, $route, $location, Albu
|
|||
$route.reload();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.requestDeleteAccess = function (album) {
|
||||
new Album(album).$delete({id: album.id}, function () {
|
||||
// no-op
|
||||
}, function () {
|
||||
document.getElementById("output").innerHTML = 'Sent authorization request to resource owner, please, wait for approval.';
|
||||
});
|
||||
}
|
||||
|
||||
$scope.hasAccess = function (share, scope) {
|
||||
for (i = 0; i < share.scopes.length; i++) {
|
||||
if (share.scopes[i] == scope) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
module.controller('TokenCtrl', function ($scope, Identity) {
|
||||
|
@ -98,7 +118,9 @@ module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location,
|
|||
});
|
||||
|
||||
module.factory('Album', ['$resource', function ($resource) {
|
||||
return $resource(apiUrl + '/album/:id');
|
||||
return $resource(apiUrl + '/album/:id', {id: '@id'}, {
|
||||
shares: {url: apiUrl + '/album/shares', method: 'GET', isArray: true}
|
||||
});
|
||||
}]);
|
||||
|
||||
module.factory('Profile', ['$resource', function ($resource) {
|
||||
|
@ -133,11 +155,46 @@ module.factory('authInterceptor', function ($q, $injector, $timeout, Identity) {
|
|||
}
|
||||
|
||||
if (rejection.config.url.indexOf('/authorize') == -1 && retry) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
// here is the authorization logic, which tries to obtain an authorization token from the server in case the resource server
|
||||
// returns a 403 or 401.
|
||||
Identity.authorization.authorize(rejection.headers('WWW-Authenticate')).then(function (rpt) {
|
||||
var wwwAuthenticateHeader = rejection.headers('WWW-Authenticate');
|
||||
|
||||
// when using UMA, a WWW-Authenticate header should be returned by the resource server
|
||||
if (!wwwAuthenticateHeader) {
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
|
||||
// when using UMA, a WWW-Authenticate header should contain UMA data
|
||||
if (wwwAuthenticateHeader.indexOf('UMA') == -1) {
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
var params = wwwAuthenticateHeader.split(',');
|
||||
var ticket;
|
||||
|
||||
// try to extract the permission ticket from the WWW-Authenticate header
|
||||
for (i = 0; i < params.length; i++) {
|
||||
var param = params[i].split('=');
|
||||
|
||||
if (param[0] == 'ticket') {
|
||||
ticket = param[1].substring(1, param[1].length - 1).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// a permission ticket must exist in order to send an authorization request
|
||||
if (!ticket) {
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
|
||||
// prepare a authorization request with the permission ticket
|
||||
var authorizationRequest = {};
|
||||
authorizationRequest.ticket = ticket;
|
||||
|
||||
// send the authorization request, if successful retry the request
|
||||
Identity.authorization.authorize(authorizationRequest).then(function (rpt) {
|
||||
deferred.resolve(rejection);
|
||||
}, function () {
|
||||
document.getElementById("output").innerHTML = 'You can not access or perform the requested operation on this resource.';
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
keycloak.logout();
|
||||
};
|
||||
|
||||
this.account = function () {
|
||||
keycloak.accountManagement();
|
||||
}
|
||||
|
||||
this.hasRole = function (name) {
|
||||
if (keycloak && keycloak.hasRealmRole(name)) {
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"realm": "photoz",
|
||||
"auth-server-url" : "http://localhost:8080/auth",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"resource" : "photoz-html5-client",
|
||||
"public-client" : true
|
||||
|
|
|
@ -5,18 +5,18 @@
|
|||
<div data-ng-show="!Identity.isAdmin()">
|
||||
<a href="#/album/create" id="create-album">Create Album</a> | <a href="#/profile">My Profile</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Your Albums</h3>
|
||||
<span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
|
||||
<table class="table" data-ng-show="albums.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Your Albums</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="p in albums">
|
||||
<td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Shared With Me</h3>
|
||||
<span data-ng-show="shares.length == 0" id="share-list-empty">You don't have any shares, yet.</span>
|
||||
<table class="table" data-ng-show="shares.length > 0">
|
||||
<tr data-ng-repeat="p in shares">
|
||||
<td><a id="view-share-{{p.album.name}}" href="#/album/{{p.album.id}}">{{p.album.name}}</a> - <a href="#" id="delete-share-{{p.album.name}}" data-ng-show="hasAccess(p, 'album:delete')" ng-click="deleteAlbum(p.album)">[X]</a><a href="#" id="request-delete-share-{{p.album.name}}" data-ng-hide="hasAccess(p, 'album:delete')" ng-click="requestDeleteAccess(p.album)">Request Delete Access</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"realm": "photoz",
|
||||
"enabled": true,
|
||||
"userManagedAccessAllowed": "true",
|
||||
"sslRequired": "external",
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
|
@ -26,6 +27,9 @@
|
|||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
],
|
||||
"account": [
|
||||
"manage-account"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -47,6 +51,9 @@
|
|||
"clientRoles": {
|
||||
"photoz-restful-api": [
|
||||
"manage-albums"
|
||||
],
|
||||
"account": [
|
||||
"manage-account"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -100,13 +107,13 @@
|
|||
{
|
||||
"clientId": "photoz-html5-client",
|
||||
"enabled": true,
|
||||
"adminUrl": "/photoz-html5-client",
|
||||
"baseUrl": "/photoz-html5-client",
|
||||
"adminUrl": "http://localhost:8080/photoz-html5-client",
|
||||
"baseUrl": "http://localhost:8080/photoz-html5-client",
|
||||
"publicClient": true,
|
||||
"consentRequired" : true,
|
||||
"fullScopeAllowed" : true,
|
||||
"redirectUris": [
|
||||
"/photoz-html5-client/*"
|
||||
"http://localhost:8080/photoz-html5-client/*"
|
||||
],
|
||||
"webOrigins": ["http://localhost:8080"]
|
||||
},
|
||||
|
@ -114,10 +121,10 @@
|
|||
"clientId": "photoz-restful-api",
|
||||
"secret": "secret",
|
||||
"enabled": true,
|
||||
"baseUrl": "/photoz-restful-api",
|
||||
"baseUrl": "http://localhost:8080/photoz-restful-api",
|
||||
"authorizationServicesEnabled" : true,
|
||||
"redirectUris": [
|
||||
"/photoz-restful-api/*"
|
||||
"http://localhost:8080/photoz-html5-client"
|
||||
],
|
||||
"webOrigins" : ["http://localhost:8080"]
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package org.keycloak.example.photoz.album;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.example.photoz.ErrorResponse;
|
||||
import org.keycloak.example.photoz.entity.Album;
|
||||
import org.keycloak.example.photoz.util.Transaction;
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.EntityManager;
|
||||
|
@ -24,18 +21,24 @@ import javax.ws.rs.Produces;
|
|||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.example.photoz.ErrorResponse;
|
||||
import org.keycloak.example.photoz.entity.Album;
|
||||
import org.keycloak.example.photoz.util.Transaction;
|
||||
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
||||
|
||||
@Path("/album")
|
||||
@Transaction
|
||||
public class AlbumService {
|
||||
|
||||
public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view";
|
||||
public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete";
|
||||
public static final String SCOPE_ALBUM_VIEW = "album:view";
|
||||
public static final String SCOPE_ALBUM_DELETE = "album:delete";
|
||||
|
||||
@Inject
|
||||
private EntityManager entityManager;
|
||||
|
@ -60,9 +63,12 @@ public class AlbumService {
|
|||
throw new ErrorResponse("Name [" + newAlbum.getName() + "] already taken. Choose another one.", Status.CONFLICT);
|
||||
}
|
||||
|
||||
try {
|
||||
this.entityManager.persist(newAlbum);
|
||||
|
||||
createProtectedResource(newAlbum);
|
||||
} catch (Exception e) {
|
||||
getAuthzClient().protection().resource().delete(newAlbum.getExternalId());
|
||||
}
|
||||
|
||||
return Response.ok(newAlbum).build();
|
||||
}
|
||||
|
@ -88,6 +94,29 @@ public class AlbumService {
|
|||
return Response.ok(this.entityManager.createQuery("from Album where userId = :id").setParameter("id", request.getUserPrincipal().getName()).getResultList()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/shares")
|
||||
@Produces("application/json")
|
||||
public Response findShares() {
|
||||
List<PermissionTicketRepresentation> permissions = getAuthzClient().protection().permission().find(null, null, null, getKeycloakSecurityContext().getToken().getSubject(), true, true, null, null);
|
||||
Map<String, SharedAlbum> shares = new HashMap<>();
|
||||
|
||||
for (PermissionTicketRepresentation permission : permissions) {
|
||||
SharedAlbum share = shares.get(permission.getResource());
|
||||
|
||||
if (share == null) {
|
||||
share = new SharedAlbum(Album.class.cast(entityManager.createQuery("from Album where externalId = :externalId").setParameter("externalId", permission.getResource()).getSingleResult()));
|
||||
shares.put(permission.getResource(), share);
|
||||
}
|
||||
|
||||
if (permission.getScope() != null) {
|
||||
share.addScope(permission.getScopeName());
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(shares.values()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@Produces("application/json")
|
||||
|
@ -111,8 +140,11 @@ public class AlbumService {
|
|||
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
|
||||
|
||||
albumResource.setOwner(album.getUserId());
|
||||
albumResource.setOwnerManagedAccess(true);
|
||||
|
||||
getAuthzClient().protection().resource().create(albumResource);
|
||||
ResourceRepresentation response = getAuthzClient().protection().resource().create(albumResource);
|
||||
|
||||
album.setExternalId(response.getId());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not register protected resource.", e);
|
||||
}
|
||||
|
@ -123,13 +155,13 @@ public class AlbumService {
|
|||
|
||||
try {
|
||||
ProtectionResource protection = getAuthzClient().protection();
|
||||
Set<String> search = protection.resource().findByFilter("uri=" + uri);
|
||||
List<ResourceRepresentation> search = protection.resource().findByUri(uri);
|
||||
|
||||
if (search.isEmpty()) {
|
||||
throw new RuntimeException("Could not find protected resource with URI [" + uri + "]");
|
||||
}
|
||||
|
||||
protection.resource().delete(search.iterator().next());
|
||||
protection.resource().delete(search.get(0).getId());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not search protected resource.", e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2017 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.example.photoz.album;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.example.photoz.entity.Album;
|
||||
|
||||
public class SharedAlbum {
|
||||
|
||||
private Album album;
|
||||
private List<String> scopes;
|
||||
|
||||
public SharedAlbum(Album album) {
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
public Album getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public List<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void addScope(String scope) {
|
||||
if (scopes == null) {
|
||||
scopes = new ArrayList<>();
|
||||
}
|
||||
scopes.add(scope);
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@
|
|||
*/
|
||||
package org.keycloak.example.photoz.entity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
|
@ -43,6 +46,9 @@ public class Album {
|
|||
@Column(nullable = false)
|
||||
private String userId;
|
||||
|
||||
@Column
|
||||
private String externalId;
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
@ -74,4 +80,12 @@ public class Album {
|
|||
public String getUserId() {
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
public void setExternalId(String externalId) {
|
||||
this.externalId = externalId;
|
||||
}
|
||||
|
||||
public String getExternalId() {
|
||||
return externalId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,23 @@
|
|||
"allowRemoteResourceManagement": true,
|
||||
"policyEnforcementMode": "ENFORCING",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Admin Resources",
|
||||
"uri": "/admin/*",
|
||||
"type": "http://photoz.com/admin",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "admin:manage"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "User Profile Resource",
|
||||
"uri": "/profile",
|
||||
"type": "http://photoz.com/profile",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:profile:view"
|
||||
"name": "profile:view"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -18,28 +28,45 @@
|
|||
"type": "http://photoz.com/album",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:view"
|
||||
"name": "album:delete"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:delete"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:create"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Admin Resources",
|
||||
"uri": "/admin/*",
|
||||
"type": "http://photoz.com/admin",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:admin:manage"
|
||||
"name": "album:view"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Only Owner and Administrators Policy",
|
||||
"description": "Defines that only the resource owner and administrators can do something",
|
||||
"type": "aggregate",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Administration Policy",
|
||||
"description": "Defines that only administrators from a specific network address can do something.",
|
||||
"type": "aggregate",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only From @keycloak.org or Admin",
|
||||
"description": "Defines that only users from @keycloak.org",
|
||||
"type": "js",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRealmRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Owner Policy",
|
||||
"description": "Defines that only the resource owner is allowed to do something",
|
||||
|
@ -66,16 +93,6 @@
|
|||
"roles": "[{\"id\":\"admin\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that only users from well known clients are allowed to access",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only From a Specific Client Address",
|
||||
"description": "Defines that only clients from a specific address can do something",
|
||||
|
@ -87,45 +104,13 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "Administration Policy",
|
||||
"description": "Defines that only administrators from a specific network address can do something.",
|
||||
"type": "aggregate",
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that only users from well known clients are allowed to access",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Only From a Specific Client Address\",\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Owner and Administrators Policy",
|
||||
"description": "Defines that only the resource owner and administrators can do something",
|
||||
"type": "aggregate",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only From @keycloak.org or Admin",
|
||||
"description": "Defines that only users from @keycloak.org",
|
||||
"type": "js",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRealmRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Album Resource Permission",
|
||||
"description": "General policies that apply to all album resources.",
|
||||
"type": "resource",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"defaultResourceType": "http://photoz.com/album",
|
||||
"default": "true",
|
||||
"applyPolicies": "[\"Any User Policy\",\"Administration Policy\"]"
|
||||
"roles": "[{\"id\":\"user\",\"required\":false},{\"id\":\"photoz-restful-api/manage-albums\",\"required\":true}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -136,8 +121,20 @@
|
|||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"defaultResourceType": "http://photoz.com/admin",
|
||||
"default": "true",
|
||||
"applyPolicies": "[\"Administration Policy\"]"
|
||||
"applyPolicies": "[\"Administration Policy\"]",
|
||||
"default": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Album Resource Permission",
|
||||
"description": "A default permission that defines access for any album resource",
|
||||
"type": "scope",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Album Resource\"]",
|
||||
"scopes": "[\"album:view\",\"album:delete\"]",
|
||||
"applyPolicies": "[\"Only Owner and Administrators Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -147,37 +144,9 @@
|
|||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Only From @keycloak.org or Admin\"]",
|
||||
"scopes": "[\"urn:photoz.com:scopes:profile:view\"]"
|
||||
"scopes": "[\"profile:view\"]",
|
||||
"applyPolicies": "[\"Only From @keycloak.org or Admin\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Delete Album Permission",
|
||||
"description": "A policy that only allows the owner to delete his albums.",
|
||||
"type": "scope",
|
||||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Only Owner and Administrators Policy\"]",
|
||||
"scopes": "[\"urn:photoz.com:scopes:album:delete\"]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:profile:view"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:view"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:create"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:delete"
|
||||
},
|
||||
{
|
||||
"name": "urn:photoz.com:scopes:album:admin:manage"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"realm": "photoz",
|
||||
"auth-server-url": "http://localhost:8080/auth",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"ssl-required": "external",
|
||||
"resource": "photoz-restful-api",
|
||||
"bearer-only" : true,
|
||||
|
@ -8,35 +8,28 @@
|
|||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"enforcement-mode": "PERMISSIVE",
|
||||
"user-managed-access": {},
|
||||
"paths": [
|
||||
{
|
||||
"path" : "/album/*",
|
||||
"methods" : [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:create"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "Album Resource",
|
||||
"path" : "/album/{id}",
|
||||
"methods" : [
|
||||
{
|
||||
"method": "DELETE",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:delete"]
|
||||
"scopes" : ["album:delete"]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes" : ["urn:photoz.com:scopes:album:view"]
|
||||
"scopes" : ["album:view"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "Album Resource",
|
||||
"path" : "/album/shares",
|
||||
"enforcement-mode": "DISABLED"
|
||||
},
|
||||
{
|
||||
"path" : "/profile"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"realm": "servlet-authz",
|
||||
"auth-server-url": "http://localhost:8080/auth",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"ssl-required": "external",
|
||||
"resource": "servlet-authz-app",
|
||||
"credentials": {
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
for (Permission permission : authzContext.getPermissions()) {
|
||||
%>
|
||||
<li>
|
||||
<p>Resource: <%= permission.getResourceSetName() %></p>
|
||||
<p>ID: <%= permission.getResourceSetId() %></p>
|
||||
<p>Resource: <%= permission.getResourceName() %></p>
|
||||
<p>ID: <%= permission.getResourceId() %></p>
|
||||
<p>Scopes: <%= permission.getScopes() %></p>
|
||||
</li>
|
||||
<%
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
String contextPath = request.getContextPath();
|
||||
String redirectUri = scheme + "://" + host + ":" + port + contextPath;
|
||||
%>
|
||||
<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8180/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
|
|
@ -141,6 +141,18 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
updated.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserManagedAccessAllowed() {
|
||||
if (isUpdated()) return updated.isEnabled();
|
||||
return cached.isAllowUserManagedAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserManagedAccessAllowed(boolean userManagedAccessAllowed) {
|
||||
getDelegateForUpdate();
|
||||
updated.setUserManagedAccessAllowed(userManagedAccessAllowed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslRequired getSslRequired() {
|
||||
if (isUpdated()) return updated.getSslRequired();
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.cache.infinispan.authorization;
|
||||
|
||||
import org.keycloak.authorization.model.CachedModel;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPermissionTicket;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PermissionTicketAdapter implements PermissionTicket, CachedModel<PermissionTicket> {
|
||||
|
||||
protected CachedPermissionTicket cached;
|
||||
protected StoreFactoryCacheSession cacheSession;
|
||||
protected PermissionTicket updated;
|
||||
|
||||
public PermissionTicketAdapter(CachedPermissionTicket cached, StoreFactoryCacheSession cacheSession) {
|
||||
this.cached = cached;
|
||||
this.cacheSession = cacheSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicket getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
||||
updated = cacheSession.getPermissionTicketStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
protected boolean invalidated;
|
||||
|
||||
protected void invalidateFlag() {
|
||||
invalidated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
invalidated = true;
|
||||
getDelegateForUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCacheTimestamp() {
|
||||
return cached.getCacheTimestamp();
|
||||
}
|
||||
|
||||
protected boolean isUpdated() {
|
||||
if (updated != null) return true;
|
||||
if (!invalidated) return false;
|
||||
updated = cacheSession.getPermissionTicketStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
if (isUpdated()) return updated.getId();
|
||||
return cached.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwner() {
|
||||
if (isUpdated()) return updated.getOwner();
|
||||
return cached.getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequester() {
|
||||
if (isUpdated()) return updated.getRequester();
|
||||
return cached.getRequester();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
if (isUpdated()) return updated.isGranted();
|
||||
return cached.isGranted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
if (isUpdated()) return updated.getCreatedTimestamp();
|
||||
return cached.getCreatedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getGrantedTimestamp() {
|
||||
if (isUpdated()) return updated.getGrantedTimestamp();
|
||||
return cached.getGrantedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGrantedTimestamp(Long millis) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
|
||||
updated.setGrantedTimestamp(millis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceServer getResourceServer() {
|
||||
return cacheSession.getResourceServerStore().findById(cached.getResourceServerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource getResource() {
|
||||
return cacheSession.getResourceStore().findById(cached.getResourceId(), getResourceServer().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
return cacheSession.getScopeStore().findById(cached.getScopeId(), getResourceServer().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
|
|||
import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
@ -206,14 +207,15 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
@Override
|
||||
public void addScope(Scope scope) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), new HashSet<>(Arrays.asList(scope.getId())), cached.getResourceServerId());
|
||||
updated.addScope(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScope(Scope scope) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), new HashSet<>(Arrays.asList(scope.getId())), cached.getResourceServerId());
|
||||
updated.removeScope(scope);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
package org.keycloak.models.cache.infinispan.authorization;
|
||||
|
||||
import org.keycloak.authorization.model.CachedModel;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -96,7 +98,19 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
if (isUpdated()) return updated.getDisplayName();
|
||||
return cached.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setDisplayName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -165,8 +179,33 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateScopes(Set<Scope> scopes) {
|
||||
public boolean isOwnerManagedAccess() {
|
||||
if (isUpdated()) return updated.isOwnerManagedAccess();
|
||||
return cached.isOwnerManagedAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.setOwnerManagedAccess(ownerManagedAccess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScopes(Set<Scope> scopes) {
|
||||
Resource updated = getDelegateForUpdate();
|
||||
|
||||
for (Scope scope : updated.getScopes()) {
|
||||
if (!scopes.contains(scope)) {
|
||||
PermissionTicketStore permissionStore = cacheSession.getPermissionTicketStoreDelegate();
|
||||
List<PermissionTicket> permissions = permissionStore.findByScope(scope.getId(), getResourceServer().getId());
|
||||
|
||||
for (PermissionTicket permission : permissions) {
|
||||
permissionStore.delete(permission.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
|
||||
updated.updateScopes(scopes);
|
||||
}
|
||||
|
|
|
@ -91,6 +91,18 @@ public class ScopeAdapter implements Scope, CachedModel<Scope> {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
if (isUpdated()) return updated.getDisplayName();
|
||||
return cached.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String name) {
|
||||
getDelegateForUpdate();
|
||||
updated.setDisplayName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconUri() {
|
||||
if (isUpdated()) return updated.getIconUri();
|
||||
|
|
|
@ -68,6 +68,7 @@ public class StoreFactoryCacheManager extends CacheManager {
|
|||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getScopeByNameCacheKey(name, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(id, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(id, serverId));
|
||||
}
|
||||
|
||||
public void scopeRemoval(String id, String name, String serverId, Set<String> invalidations) {
|
||||
|
@ -79,6 +80,8 @@ public class StoreFactoryCacheManager extends CacheManager {
|
|||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, null));
|
||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(id, serverId));
|
||||
|
||||
if (type != null) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
|
||||
|
@ -125,9 +128,21 @@ public class StoreFactoryCacheManager extends CacheManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void permissionTicketUpdated(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
|
||||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByOwner(owner, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(resource, serverId));
|
||||
if (scope != null) {
|
||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));
|
||||
}
|
||||
}
|
||||
|
||||
public void policyRemoval(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId, Set<String> invalidations) {
|
||||
policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
|
||||
}
|
||||
|
||||
public void permissionTicketRemoval(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
|
||||
permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,10 +30,12 @@ import java.util.function.Supplier;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
|
@ -43,10 +45,15 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPermissionTicket;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PermissionTicketListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PermissionTicketQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PermissionTicketResourceListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PermissionTicketScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyResourceListQuery;
|
||||
|
@ -55,6 +62,8 @@ import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQ
|
|||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.PermissionTicketRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.PermissionTicketUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.PolicyUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.ResourceRemovedEvent;
|
||||
|
@ -82,6 +91,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
protected Map<String, ScopeAdapter> managedScopes = new HashMap<>();
|
||||
protected Map<String, ResourceAdapter> managedResources = new HashMap<>();
|
||||
protected Map<String, PolicyAdapter> managedPolicies = new HashMap<>();
|
||||
protected Map<String, PermissionTicketAdapter> managedPermissionTickets = new HashMap<>();
|
||||
protected Set<String> invalidations = new HashSet<>();
|
||||
protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster
|
||||
|
||||
|
@ -93,6 +103,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
protected ScopeCache scopeCache;
|
||||
protected ResourceCache resourceCache;
|
||||
protected PolicyCache policyCache;
|
||||
protected PermissionTicketCache permissionTicketCache;
|
||||
|
||||
public StoreFactoryCacheSession(StoreFactoryCacheManager cache, KeycloakSession session) {
|
||||
this.cache = cache;
|
||||
|
@ -102,6 +113,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
this.scopeCache = new ScopeCache();
|
||||
this.resourceCache = new ResourceCache();
|
||||
this.policyCache = new PolicyCache();
|
||||
this.permissionTicketCache = new PermissionTicketCache();
|
||||
session.getTransactionManager().enlistPrepare(getPrepareTransaction());
|
||||
session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
|
||||
}
|
||||
|
@ -126,6 +138,11 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
return policyCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicketStore getPermissionTicketStore() {
|
||||
return permissionTicketCache;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
|
@ -263,6 +280,14 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
|
||||
}
|
||||
|
||||
public void registerPermissionTicketInvalidation(String id, String owner, String resource, String scope, String serverId) {
|
||||
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
||||
PermissionTicketAdapter adapter = managedPermissionTickets.get(id);
|
||||
if (adapter != null) adapter.invalidateFlag();
|
||||
|
||||
invalidationEvents.add(PermissionTicketUpdatedEvent.create(id, owner, resource, scope, serverId));
|
||||
}
|
||||
|
||||
private Set<String> getResourceTypes(Set<String> resources, String serverId) {
|
||||
if (resources == null) {
|
||||
return Collections.emptySet();
|
||||
|
@ -296,6 +321,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
return getDelegate().getPolicyStore();
|
||||
}
|
||||
|
||||
public PermissionTicketStore getPermissionTicketStoreDelegate() {
|
||||
return getDelegate().getPermissionTicketStore();
|
||||
}
|
||||
|
||||
public static String getResourceServerByClientCacheKey(String clientId) {
|
||||
return "resource.server.client.id." + clientId;
|
||||
}
|
||||
|
@ -340,6 +369,18 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
return "policy.scope." + scope + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPermissionTicketByResource(String resourceId, String serverId) {
|
||||
return "permission.ticket.resource." + resourceId + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPermissionTicketByScope(String scopeId, String serverId) {
|
||||
return "permission.ticket.scope." + scopeId + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPermissionTicketByOwner(String owner, String serverId) {
|
||||
return "permission.ticket.owner." + owner + "." + serverId;
|
||||
}
|
||||
|
||||
public StoreFactory getDelegate() {
|
||||
if (delegate != null) return delegate;
|
||||
delegate = session.getProvider(StoreFactory.class);
|
||||
|
@ -592,7 +633,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
|
||||
@Override
|
||||
public List<Resource> findByType(String type, String resourceServerId) {
|
||||
if (type == null) return null;
|
||||
if (type == null) return Collections.emptyList();
|
||||
String cacheKey = getResourceByTypeCacheKey(type, resourceServerId);
|
||||
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, resourceServerId),
|
||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
|
@ -761,5 +802,108 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
}
|
||||
}
|
||||
|
||||
protected class PermissionTicketCache implements PermissionTicketStore {
|
||||
@Override
|
||||
public PermissionTicket create(String resourceId, String scopeId, String requester, ResourceServer resourceServer) {
|
||||
PermissionTicket created = getPermissionTicketStoreDelegate().create(resourceId, scopeId, requester, resourceServer);
|
||||
registerPermissionTicketInvalidation(created.getId(), created.getOwner(), created.getResource().getId(), scopeId, created.getResourceServer().getId());
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
if (id == null) return;
|
||||
PermissionTicket permission = findById(id, null);
|
||||
if (permission == null) return;
|
||||
|
||||
cache.invalidateObject(id);
|
||||
String scopeId = null;
|
||||
if (permission.getScope() != null) {
|
||||
scopeId = permission.getScope().getId();
|
||||
}
|
||||
invalidationEvents.add(PermissionTicketRemovedEvent.create(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId()));
|
||||
cache.permissionTicketRemoval(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId(), invalidations);
|
||||
getPermissionTicketStoreDelegate().delete(id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicket findById(String id, String resourceServerId) {
|
||||
if (id == null) return null;
|
||||
|
||||
CachedPermissionTicket cached = cache.get(id, CachedPermissionTicket.class);
|
||||
if (cached != null) {
|
||||
logger.tracev("by id cache hit: {0}", cached.getId());
|
||||
}
|
||||
boolean wasCached = false;
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
PermissionTicket model = getPermissionTicketStoreDelegate().findById(id, resourceServerId);
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedPermissionTicket(loaded, model);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
wasCached =true;
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getPermissionTicketStoreDelegate().findById(id, resourceServerId);
|
||||
} else if (managedPermissionTickets.containsKey(id)) {
|
||||
return managedPermissionTickets.get(id);
|
||||
}
|
||||
PermissionTicketAdapter adapter = new PermissionTicketAdapter(cached, StoreFactoryCacheSession.this);
|
||||
managedPermissionTickets.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByResourceServer(String resourceServerId) {
|
||||
return getPermissionTicketStoreDelegate().findByResourceServer(resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByResource(String resourceId, String resourceServerId) {
|
||||
String cacheKey = getPermissionTicketByResource(resourceId, resourceServerId);
|
||||
return cacheQuery(cacheKey, PermissionTicketResourceListQuery.class, () -> getPermissionTicketStoreDelegate().findByResource(resourceId, resourceServerId),
|
||||
(revision, permissions) -> new PermissionTicketResourceListQuery(revision, cacheKey, resourceId, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByScope(String scopeId, String resourceServerId) {
|
||||
String cacheKey = getPermissionTicketByScope(scopeId, resourceServerId);
|
||||
return cacheQuery(cacheKey, PermissionTicketScopeListQuery.class, () -> getPermissionTicketStoreDelegate().findByScope(scopeId, resourceServerId),
|
||||
(revision, permissions) -> new PermissionTicketScopeListQuery(revision, cacheKey, scopeId, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> find(Map<String, String> attributes, String resourceServerId, int firstResult, int maxResult) {
|
||||
return getPermissionTicketStoreDelegate().find(attributes, resourceServerId, firstResult, maxResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
||||
String cacheKey = getPermissionTicketByOwner(owner, resourceServerId);
|
||||
return cacheQuery(cacheKey, PermissionTicketListQuery.class, () -> getPermissionTicketStoreDelegate().findByOwner(owner, resourceServerId),
|
||||
(revision, permissions) -> new PermissionTicketListQuery(revision, cacheKey, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
private <R, Q extends PermissionTicketQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
|
||||
Q query = cache.get(cacheKey, queryType);
|
||||
if (query != null) {
|
||||
logger.tracev("cache hit for key: {0}", cacheKey);
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
List<R> model = resultSupplier.get();
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(cacheKey)) return model;
|
||||
query = querySupplier.apply(loaded, model);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (query.isInvalid(invalidations)) {
|
||||
return resultSupplier.get();
|
||||
} else {
|
||||
return query.getPermissions().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class CachedPermissionTicket extends AbstractRevisioned implements InResourceServer {
|
||||
|
||||
private final String requester;
|
||||
private String owner;
|
||||
private String resourceServerId;
|
||||
private String resourceId;
|
||||
private String scopeId;
|
||||
private boolean granted;
|
||||
private Long createdTimestamp;
|
||||
private Long grantedTimestamp;
|
||||
|
||||
public CachedPermissionTicket(Long revision, PermissionTicket permissionTicket) {
|
||||
super(revision, permissionTicket.getId());
|
||||
this.owner = permissionTicket.getOwner();
|
||||
requester = permissionTicket.getRequester();
|
||||
this.resourceServerId = permissionTicket.getResourceServer().getId();
|
||||
this.resourceId = permissionTicket.getResource().getId();
|
||||
if (permissionTicket.getScope() != null) {
|
||||
this.scopeId = permissionTicket.getScope().getId();
|
||||
}
|
||||
this.granted = permissionTicket.isGranted();
|
||||
createdTimestamp = permissionTicket.getCreatedTimestamp();
|
||||
grantedTimestamp = permissionTicket.getGrantedTimestamp();
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getRequester() {
|
||||
return requester;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public String getScopeId() {
|
||||
return scopeId;
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
|
||||
public long getCreatedTimestamp() {
|
||||
return createdTimestamp;
|
||||
}
|
||||
|
||||
public Long getGrantedTimestamp() {
|
||||
return grantedTimestamp;
|
||||
}
|
||||
|
||||
public String getResourceServerId() {
|
||||
return this.resourceServerId;
|
||||
}
|
||||
|
||||
}
|
|
@ -35,18 +35,22 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
|||
private String owner;
|
||||
private String type;
|
||||
private String name;
|
||||
private String displayName;
|
||||
private String uri;
|
||||
private Set<String> scopesIds;
|
||||
private boolean ownerManagedAccess;
|
||||
|
||||
public CachedResource(Long revision, Resource resource) {
|
||||
super(revision, resource.getId());
|
||||
this.name = resource.getName();
|
||||
this.displayName = resource.getDisplayName();
|
||||
this.uri = resource.getUri();
|
||||
this.type = resource.getType();
|
||||
this.owner = resource.getOwner();
|
||||
this.iconUri = resource.getIconUri();
|
||||
this.resourceServerId = resource.getResourceServer().getId();
|
||||
this.scopesIds = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
|
||||
ownerManagedAccess = resource.isOwnerManagedAccess();
|
||||
}
|
||||
|
||||
|
||||
|
@ -54,6 +58,10 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
@ -70,6 +78,10 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
|
|||
return this.owner;
|
||||
}
|
||||
|
||||
public boolean isOwnerManagedAccess() {
|
||||
return ownerManagedAccess;
|
||||
}
|
||||
|
||||
public String getResourceServerId() {
|
||||
return this.resourceServerId;
|
||||
}
|
||||
|
|
|
@ -28,11 +28,13 @@ public class CachedScope extends AbstractRevisioned implements InResourceServer
|
|||
|
||||
private String resourceServerId;
|
||||
private String name;
|
||||
private String displayName;
|
||||
private String iconUri;
|
||||
|
||||
public CachedScope(Long revision, Scope scope) {
|
||||
super(revision, scope.getId());
|
||||
this.name = scope.getName();
|
||||
this.displayName = scope.getDisplayName();
|
||||
this.iconUri = scope.getIconUri();
|
||||
this.resourceServerId = scope.getResourceServer().getId();
|
||||
}
|
||||
|
@ -41,6 +43,10 @@ public class CachedScope extends AbstractRevisioned implements InResourceServer
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getIconUri() {
|
||||
return this.iconUri;
|
||||
}
|
||||
|
@ -49,5 +55,4 @@ public class CachedScope extends AbstractRevisioned implements InResourceServer
|
|||
public String getResourceServerId() {
|
||||
return this.resourceServerId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PermissionTicketListQuery extends AbstractRevisioned implements PermissionTicketQuery {
|
||||
|
||||
private final Set<String> permissions;
|
||||
private final String serverId;
|
||||
|
||||
public PermissionTicketListQuery(Long revision, String id, String permissionId, String serverId) {
|
||||
super(revision, id);
|
||||
this.serverId = serverId;
|
||||
permissions = new HashSet<>();
|
||||
permissions.add(permissionId);
|
||||
}
|
||||
public PermissionTicketListQuery(Long revision, String id, Set<String> permissions, String serverId) {
|
||||
super(revision, id);
|
||||
this.serverId = serverId;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public Set<String> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return invalidations.contains(getId()) || invalidations.contains(getResourceServerId());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
* Copyright 2017 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.
|
||||
|
@ -15,28 +14,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class EntitlementResponse {
|
||||
public interface PermissionTicketQuery extends InResourceServer, Revisioned {
|
||||
|
||||
private String rpt;
|
||||
|
||||
public EntitlementResponse(String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
|
||||
public EntitlementResponse() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public String getRpt() {
|
||||
return this.rpt;
|
||||
}
|
||||
|
||||
public void setRpt(final String rpt) {
|
||||
this.rpt = rpt;
|
||||
}
|
||||
Set<String> getPermissions();
|
||||
boolean isInvalid(Set<String> invalidations);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionTicketResourceListQuery extends PermissionTicketListQuery implements InResource {
|
||||
|
||||
private final String resourceId;
|
||||
|
||||
public PermissionTicketResourceListQuery(Long revision, String id, String resourceId, Set<String> permissions, String serverId) {
|
||||
super(revision, id, permissions, serverId);
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return super.isInvalid(invalidations) || invalidations.contains(getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
}
|
|
@ -14,35 +14,29 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.authorization.client.representation;
|
||||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AuthorizationRequestMetadata {
|
||||
public class PermissionTicketScopeListQuery extends PermissionTicketListQuery implements InScope {
|
||||
|
||||
public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
|
||||
private final String scopeId;
|
||||
|
||||
@JsonProperty(INCLUDE_RESOURCE_NAME)
|
||||
private boolean includeResourceName = true;
|
||||
|
||||
private int limit;
|
||||
|
||||
public boolean isIncludeResourceName() {
|
||||
return includeResourceName;
|
||||
public PermissionTicketScopeListQuery(Long revision, String id, String scopeId, Set<String> permissions, String serverId) {
|
||||
super(revision, id, permissions, serverId);
|
||||
this.scopeId = scopeId;
|
||||
}
|
||||
|
||||
public void setIncludeResourceName(boolean includeResourceName) {
|
||||
this.includeResourceName = includeResourceName;
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return super.isInvalid(invalidations) || invalidations.contains(getScopeId());
|
||||
}
|
||||
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
@Override
|
||||
public String getScopeId() {
|
||||
return scopeId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.cache.infinispan.authorization.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PermissionTicketRemovedEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
|
||||
|
||||
private String id;
|
||||
private String owner;
|
||||
private String resource;
|
||||
private String scope;
|
||||
private String serverId;
|
||||
|
||||
public static PermissionTicketRemovedEvent create(String id, String owner, String resource, String scope, String serverId) {
|
||||
PermissionTicketRemovedEvent event = new PermissionTicketRemovedEvent();
|
||||
event.id = id;
|
||||
event.owner = owner;
|
||||
event.resource = resource;
|
||||
event.scope = scope;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PermissionTicketRemovedEvent [ id=%s, name=%s]", id, resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.permissionTicketRemoval(id, owner, resource, scope, serverId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.cache.infinispan.authorization.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PermissionTicketUpdatedEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
|
||||
|
||||
private String id;
|
||||
private String owner;
|
||||
private String resource;
|
||||
private String scope;
|
||||
private String serverId;
|
||||
|
||||
public static PermissionTicketUpdatedEvent create(String id, String owner, String resource, String scope, String serverId) {
|
||||
PermissionTicketUpdatedEvent event = new PermissionTicketUpdatedEvent();
|
||||
event.id = id;
|
||||
event.owner = owner;
|
||||
event.resource = resource;
|
||||
event.scope = scope;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PermissionTicketUpdatedEvent [ id=%s, name=%s]", id, resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
|
||||
}
|
||||
}
|
|
@ -128,6 +128,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
|
||||
protected boolean adminEventsDetailsEnabled;
|
||||
protected List<String> defaultRoles;
|
||||
private boolean allowUserManagedAccess;
|
||||
|
||||
public Set<IdentityProviderMapperModel> getIdentityProviderMapperSet() {
|
||||
return identityProviderMapperSet;
|
||||
|
@ -151,6 +152,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
displayName = model.getDisplayName();
|
||||
displayNameHtml = model.getDisplayNameHtml();
|
||||
enabled = model.isEnabled();
|
||||
allowUserManagedAccess = model.isUserManagedAccessAllowed();
|
||||
sslRequired = model.getSslRequired();
|
||||
registrationAllowed = model.isRegistrationAllowed();
|
||||
registrationEmailAsUsername = model.isRegistrationEmailAsUsername();
|
||||
|
@ -629,4 +631,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public boolean isAllowUserManagedAccess() {
|
||||
return allowUserManagedAccess;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright 2017 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.jpa.entities;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "RESOURCE_SERVER_PERMISSION_TICKET", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"OWNER", "RESOURCE_SERVER_ID", "RESOURCE_ID", "SCOPE_ID"})
|
||||
})
|
||||
@NamedQueries(
|
||||
{
|
||||
@NamedQuery(name="findPermissionIdByResource", query="select p.id from PermissionTicketEntity p inner join p.resource r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"),
|
||||
@NamedQuery(name="findPermissionIdByScope", query="select p.id from PermissionTicketEntity p inner join p.scope s where p.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id = :scopeId)"),
|
||||
@NamedQuery(name="findPermissionTicketIdByServerId", query="select p.id from PermissionTicketEntity p where p.resourceServer.id = :serverId ")
|
||||
}
|
||||
)
|
||||
public class PermissionTicketEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "ID", length = 36)
|
||||
@Access(AccessType.PROPERTY)
|
||||
// we do this because relationships often fetch id, but not entity. This avoids an extra SQL
|
||||
private String id;
|
||||
|
||||
@Column(name = "OWNER")
|
||||
private String owner;
|
||||
|
||||
@Column(name = "REQUESTER")
|
||||
private String requester;
|
||||
|
||||
@Column(name = "CREATED_TIMESTAMP")
|
||||
private Long createdTimestamp;
|
||||
|
||||
@Column(name = "GRANTED_TIMESTAMP")
|
||||
private Long grantedTimestamp;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "RESOURCE_ID")
|
||||
private ResourceEntity resource;
|
||||
|
||||
@ManyToOne(optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "SCOPE_ID")
|
||||
private ScopeEntity scope;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "RESOURCE_SERVER_ID")
|
||||
private ResourceServerEntity resourceServer;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public ResourceEntity getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(ResourceEntity resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public ScopeEntity getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(ScopeEntity scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public ResourceServerEntity getResourceServer() {
|
||||
return resourceServer;
|
||||
}
|
||||
|
||||
public void setResourceServer(ResourceServerEntity resourceServer) {
|
||||
this.resourceServer = resourceServer;
|
||||
}
|
||||
|
||||
public void setRequester(String requester) {
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
public String getRequester() {
|
||||
return requester;
|
||||
}
|
||||
|
||||
public Long getCreatedTimestamp() {
|
||||
return createdTimestamp;
|
||||
}
|
||||
|
||||
public void setCreatedTimestamp(Long createdTimestamp) {
|
||||
this.createdTimestamp = createdTimestamp;
|
||||
}
|
||||
|
||||
public Long getGrantedTimestamp() {
|
||||
return grantedTimestamp;
|
||||
}
|
||||
|
||||
public void setGrantedTimestamp(long grantedTimestamp) {
|
||||
this.grantedTimestamp = grantedTimestamp;
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
return grantedTimestamp != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PermissionTicketEntity that = (PermissionTicketEntity) o;
|
||||
|
||||
return getId().equals(that.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ import java.util.List;
|
|||
@NamedQueries(
|
||||
{
|
||||
@NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :owner"),
|
||||
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"),
|
||||
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.uri = :uri"),
|
||||
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.name = :name"),
|
||||
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.type = :type"),
|
||||
|
@ -63,6 +64,9 @@ public class ResourceEntity {
|
|||
@Column(name = "NAME")
|
||||
private String name;
|
||||
|
||||
@Column(name = "DISPLAY_NAME")
|
||||
private String displayName;
|
||||
|
||||
@Column(name = "URI")
|
||||
private String uri;
|
||||
|
||||
|
@ -75,6 +79,9 @@ public class ResourceEntity {
|
|||
@Column(name = "OWNER")
|
||||
private String owner;
|
||||
|
||||
@Column(name = "OWNER_MANAGED_ACCESS")
|
||||
private boolean ownerManagedAccess;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "RESOURCE_SERVER_ID")
|
||||
private ResourceServerEntity resourceServer;
|
||||
|
@ -103,6 +110,14 @@ public class ResourceEntity {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
@ -147,6 +162,14 @@ public class ResourceEntity {
|
|||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
|
||||
this.ownerManagedAccess = ownerManagedAccess;
|
||||
}
|
||||
|
||||
public boolean isOwnerManagedAccess() {
|
||||
return ownerManagedAccess;
|
||||
}
|
||||
|
||||
public List<PolicyEntity> getPolicies() {
|
||||
return this.policies;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,9 @@ public class ScopeEntity {
|
|||
@Column(name = "NAME")
|
||||
private String name;
|
||||
|
||||
@Column(name = "DISPLAY_NAME")
|
||||
private String displayName;
|
||||
|
||||
@Column(name = "ICON_URI")
|
||||
private String iconUri;
|
||||
|
||||
|
@ -86,6 +89,18 @@ public class ScopeEntity {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
if (displayName != null && !"".equals(displayName.trim())) {
|
||||
this.displayName = displayName;
|
||||
} else {
|
||||
this.displayName = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getIconUri() {
|
||||
return iconUri;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright 2017 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.jpa.store;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class JPAPermissionTicketStore implements PermissionTicketStore {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final AuthorizationProvider provider;
|
||||
|
||||
public JPAPermissionTicketStore(EntityManager entityManager, AuthorizationProvider provider) {
|
||||
this.entityManager = entityManager;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicket create(String resourceId, String scopeId, String requester, ResourceServer resourceServer) {
|
||||
PermissionTicketEntity entity = new PermissionTicketEntity();
|
||||
|
||||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setResource(ResourceAdapter.toEntity(entityManager, provider.getStoreFactory().getResourceStore().findById(resourceId, resourceServer.getId())));
|
||||
entity.setRequester(requester);
|
||||
entity.setCreatedTimestamp(System.currentTimeMillis());
|
||||
|
||||
if (scopeId != null) {
|
||||
entity.setScope(ScopeAdapter.toEntity(entityManager, provider.getStoreFactory().getScopeStore().findById(scopeId, resourceServer.getId())));
|
||||
}
|
||||
|
||||
entity.setOwner(entity.getResource().getOwner());
|
||||
entity.setResourceServer(ResourceServerAdapter.toEntity(entityManager, resourceServer));
|
||||
|
||||
this.entityManager.persist(entity);
|
||||
this.entityManager.flush();
|
||||
PermissionTicket model = new PermissionTicketAdapter(entity, entityManager, provider.getStoreFactory());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
PermissionTicketEntity policy = entityManager.find(PermissionTicketEntity.class, id);
|
||||
if (policy != null) {
|
||||
this.entityManager.remove(policy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PermissionTicket findById(String id, String resourceServerId) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PermissionTicketEntity entity = entityManager.find(PermissionTicketEntity.class, id);
|
||||
if (entity == null) return null;
|
||||
|
||||
return new PermissionTicketAdapter(entity, entityManager, provider.getStoreFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByResourceServer(final String resourceServerId) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByServerId", String.class);
|
||||
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<PermissionTicket> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByResource(final String resourceId, String resourceServerId) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPermissionIdByResource", String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("resourceId", resourceId);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<PermissionTicket> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByScope(String scopeId, String resourceServerId) {
|
||||
if (scopeId==null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Use separate subquery to handle DB2 and MSSSQL
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPermissionIdByScope", String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("scopeId", scopeId);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<PermissionTicket> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> find(Map<String, String> attributes, String resourceServerId, int firstResult, int maxResult) {
|
||||
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<PermissionTicketEntity> querybuilder = builder.createQuery(PermissionTicketEntity.class);
|
||||
Root<PermissionTicketEntity> root = querybuilder.from(PermissionTicketEntity.class);
|
||||
|
||||
querybuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList();
|
||||
|
||||
if (resourceServerId != null) {
|
||||
predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId));
|
||||
}
|
||||
|
||||
attributes.forEach((name, value) -> {
|
||||
if (PermissionTicket.ID.equals(name)) {
|
||||
predicates.add(root.get(name).in(value));
|
||||
} else if (PermissionTicket.SCOPE.equals(name)) {
|
||||
predicates.add(root.join("scope").get("id").in(value));
|
||||
} else if (PermissionTicket.SCOPE_IS_NULL.equals(name)) {
|
||||
if (Boolean.valueOf(value)) {
|
||||
predicates.add(builder.isNull(root.get("scope")));
|
||||
} else {
|
||||
predicates.add(builder.isNotNull(root.get("scope")));
|
||||
}
|
||||
} else if (PermissionTicket.RESOURCE.equals(name)) {
|
||||
predicates.add(root.join("resource").get("id").in(value));
|
||||
} else if (PermissionTicket.OWNER.equals(name)) {
|
||||
predicates.add(builder.equal(root.get("owner"), value));
|
||||
} else if (PermissionTicket.REQUESTER.equals(name)) {
|
||||
predicates.add(builder.equal(root.get("requester"), value));
|
||||
} else if (PermissionTicket.GRANTED.equals(name)) {
|
||||
if (Boolean.valueOf(value)) {
|
||||
predicates.add(builder.isNotNull(root.get("grantedTimestamp")));
|
||||
} else {
|
||||
predicates.add(builder.isNull(root.get("grantedTimestamp")));
|
||||
}
|
||||
} else if (PermissionTicket.REQUESTER_IS_NULL.equals(name)) {
|
||||
predicates.add(builder.isNull(root.get("requester")));
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported filter [" + name + "]");
|
||||
}
|
||||
});
|
||||
|
||||
querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("resource").get("id")));
|
||||
|
||||
Query query = entityManager.createQuery(querybuilder);
|
||||
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResult != -1) {
|
||||
query.setMaxResults(maxResult);
|
||||
}
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<PermissionTicket> list = new LinkedList<>();
|
||||
PermissionTicketStore ticket = provider.getStoreFactory().getPermissionTicketStore();
|
||||
|
||||
for (String id : result) {
|
||||
list.add(ticket.findById(id, resourceServerId));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByType", String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
query.setParameter("owner", owner);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<PermissionTicket> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
|
@ -18,11 +18,11 @@
|
|||
package org.keycloak.authorization.jpa.store;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
|
||||
import org.keycloak.authorization.jpa.entities.PolicyEntity;
|
||||
import org.keycloak.authorization.jpa.entities.ResourceEntity;
|
||||
import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
|
||||
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -30,7 +30,6 @@ import org.keycloak.storage.StorageId;
|
|||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -77,6 +76,17 @@ public class JPAResourceServerStore implements ResourceServerStore {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPermissionTicketIdByServerId", String.class);
|
||||
|
||||
query.setParameter("serverId", id);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
for (String permissionId : result) {
|
||||
entityManager.remove(entityManager.getReference(PermissionTicketEntity.class, permissionId));
|
||||
}
|
||||
}
|
||||
|
||||
//entityManager.createNamedQuery("deleteResourceByResourceServer")
|
||||
// .setParameter("serverId", id).executeUpdate();
|
||||
{
|
||||
|
@ -85,7 +95,6 @@ public class JPAResourceServerStore implements ResourceServerStore {
|
|||
query.setParameter("serverId", id);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<Resource> list = new LinkedList<>();
|
||||
for (String resourceId : result) {
|
||||
entityManager.remove(entityManager.getReference(ResourceEntity.class, resourceId));
|
||||
}
|
||||
|
|
|
@ -88,11 +88,20 @@ public class JPAResourceStore implements ResourceStore {
|
|||
|
||||
@Override
|
||||
public List<Resource> findByOwner(String ownerId, String resourceServerId) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByOwner", String.class);
|
||||
String queryName = "findResourceIdByOwner";
|
||||
|
||||
if (resourceServerId == null) {
|
||||
queryName = "findAnyResourceIdByOwner";
|
||||
}
|
||||
|
||||
TypedQuery<String> query = entityManager.createNamedQuery(queryName, String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("owner", ownerId);
|
||||
|
||||
if (resourceServerId != null) {
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
}
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<Resource> list = new LinkedList<>();
|
||||
|
@ -161,13 +170,17 @@ public class JPAResourceStore implements ResourceStore {
|
|||
querybuilder.select(root.get("id"));
|
||||
List<Predicate> predicates = new ArrayList();
|
||||
|
||||
if (resourceServerId != null) {
|
||||
predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId));
|
||||
}
|
||||
|
||||
attributes.forEach((name, value) -> {
|
||||
if ("id".equals(name)) {
|
||||
predicates.add(root.get(name).in(value));
|
||||
} else if ("scope".equals(name)) {
|
||||
predicates.add(root.join("scopes").get("id").in(value));
|
||||
} else if ("ownerManagedAccess".equals(name)) {
|
||||
predicates.add(builder.equal(root.get(name), Boolean.valueOf(value[0])));
|
||||
} else {
|
||||
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.keycloak.authorization.jpa.store;
|
|||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
|
@ -36,12 +37,14 @@ public class JPAStoreFactory implements StoreFactory {
|
|||
private final ResourceServerStore resourceServerStore;
|
||||
private final ResourceStore resourceStore;
|
||||
private final ScopeStore scopeStore;
|
||||
private final JPAPermissionTicketStore permissionTicketStore;
|
||||
|
||||
public JPAStoreFactory(EntityManager entityManager, AuthorizationProvider provider) {
|
||||
policyStore = new JPAPolicyStore(entityManager, provider);
|
||||
resourceServerStore = new JPAResourceServerStore(entityManager, provider);
|
||||
resourceStore = new JPAResourceStore(entityManager, provider);
|
||||
scopeStore = new JPAScopeStore(entityManager, provider);
|
||||
permissionTicketStore = new JPAPermissionTicketStore(entityManager, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,6 +67,11 @@ public class JPAStoreFactory implements StoreFactory {
|
|||
return scopeStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicketStore getPermissionTicketStore() {
|
||||
return permissionTicketStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2017 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.jpa.store;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
|
||||
import org.keycloak.authorization.jpa.entities.ScopeEntity;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.models.jpa.JpaModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PermissionTicketAdapter implements PermissionTicket, JpaModel<PermissionTicketEntity> {
|
||||
|
||||
private PermissionTicketEntity entity;
|
||||
private EntityManager em;
|
||||
private StoreFactory storeFactory;
|
||||
|
||||
public PermissionTicketAdapter(PermissionTicketEntity entity, EntityManager em, StoreFactory storeFactory) {
|
||||
this.entity = entity;
|
||||
this.em = em;
|
||||
this.storeFactory = storeFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicketEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwner() {
|
||||
return entity.getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequester() {
|
||||
return entity.getRequester();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return entity.isGranted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
return entity.getCreatedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getGrantedTimestamp() {
|
||||
return entity.getGrantedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGrantedTimestamp(Long millis) {
|
||||
entity.setGrantedTimestamp(millis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceServer getResourceServer() {
|
||||
return storeFactory.getResourceServerStore().findById(entity.getResourceServer().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource getResource() {
|
||||
return storeFactory.getResourceStore().findById(entity.getResource().getId(), getResourceServer().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
ScopeEntity scope = entity.getScope();
|
||||
|
||||
if (scope == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return storeFactory.getScopeStore().findById(scope.getId(), getResourceServer().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof Policy)) return false;
|
||||
|
||||
PermissionTicket that = (PermissionTicket) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
public static PermissionTicketEntity toEntity(EntityManager em, PermissionTicket permission) {
|
||||
if (permission instanceof PermissionTicketAdapter) {
|
||||
return ((PermissionTicketAdapter)permission).getEntity();
|
||||
} else {
|
||||
return em.getReference(PermissionTicketEntity.class, permission.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -62,6 +62,16 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
|
|||
return entity.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return entity.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String name) {
|
||||
entity.setDisplayName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
entity.setName(name);
|
||||
|
@ -121,6 +131,16 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
|
|||
return entity.getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnerManagedAccess() {
|
||||
return entity.isOwnerManagedAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
|
||||
entity.setOwnerManagedAccess(ownerManagedAccess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScopes(Set<Scope> toUpdate) {
|
||||
Set<String> ids = new HashSet<>();
|
||||
|
|
|
@ -60,6 +60,16 @@ public class ScopeAdapter implements Scope, JpaModel<ScopeEntity> {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return entity.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String name) {
|
||||
entity.setDisplayName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconUri() {
|
||||
return entity.getIconUri();
|
||||
|
|
|
@ -114,6 +114,17 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserManagedAccessAllowed() {
|
||||
return realm.isAllowUserManagedAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserManagedAccessAllowed(boolean userManagedAccessAllowed) {
|
||||
realm.setAllowUserManagedAccess(userManagedAccessAllowed);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistrationAllowed() {
|
||||
return realm.isRegistrationAllowed();
|
||||
|
|
|
@ -237,6 +237,9 @@ public class RealmEntity {
|
|||
@Column(name="DEFAULT_LOCALE")
|
||||
protected String defaultLocale;
|
||||
|
||||
@Column(name="ALLOW_USER_MANAGED_ACCESS")
|
||||
private boolean allowUserManagedAccess;
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -762,6 +765,14 @@ public class RealmEntity {
|
|||
this.clientTemplates = clientTemplates;
|
||||
}
|
||||
|
||||
public void setAllowUserManagedAccess(boolean allowUserManagedAccess) {
|
||||
this.allowUserManagedAccess = allowUserManagedAccess;
|
||||
}
|
||||
|
||||
public boolean isAllowUserManagedAccess() {
|
||||
return allowUserManagedAccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -779,6 +790,5 @@ public class RealmEntity {
|
|||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
78
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.CR1.xml
Executable file
78
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.CR1.xml
Executable file
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
~ * Copyright 2017 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.
|
||||
-->
|
||||
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<changeSet author="psilva@redhat.com" id="authz-3.3.0.CR1">
|
||||
<createTable tableName="RESOURCE_SERVER_PERMISSION_TICKET">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="OWNER" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REQUESTER" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CREATED_TIMESTAMP" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="GRANTED_TIMESTAMP" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="RESOURCE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SCOPE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_FAPMT" tableName="RESOURCE_SERVER_PERMISSION_TICKET"/>
|
||||
<addForeignKeyConstraint baseColumnNames="RESOURCE_SERVER_ID" baseTableName="RESOURCE_SERVER_PERMISSION_TICKET" constraintName="FK_FRSRHO213XCX4WNKOG82SSPMT" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER"/>
|
||||
<addForeignKeyConstraint baseColumnNames="RESOURCE_ID" baseTableName="RESOURCE_SERVER_PERMISSION_TICKET" constraintName="FK_FRSRHO213XCX4WNKOG83SSPMT" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_RESOURCE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="SCOPE_ID" baseTableName="RESOURCE_SERVER_PERMISSION_TICKET" constraintName="FK_FRSRHO213XCX4WNKOG84SSPMT" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_SCOPE"/>
|
||||
<addUniqueConstraint columnNames="OWNER, REQUESTER, RESOURCE_SERVER_ID, RESOURCE_ID, SCOPE_ID" constraintName="UK_FRSR6T700S9V50BU18WS5PMT" tableName="RESOURCE_SERVER_PERMISSION_TICKET"/>
|
||||
|
||||
<addColumn tableName="RESOURCE_SERVER_RESOURCE">
|
||||
<column name="OWNER_MANAGED_ACCESS" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<addColumn tableName="RESOURCE_SERVER_RESOURCE">
|
||||
<column name="DISPLAY_NAME" type="VARCHAR(255)" >
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<addColumn tableName="RESOURCE_SERVER_SCOPE">
|
||||
<column name="DISPLAY_NAME" type="VARCHAR(255)" >
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<addColumn tableName="REALM">
|
||||
<column name="ALLOW_USER_MANAGED_ACCESS" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -54,4 +54,5 @@
|
|||
<include file="META-INF/jpa-changelog-3.4.1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
||||
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-authz-4.0.0.CR1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
<class>org.keycloak.authorization.jpa.entities.ResourceEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.PermissionTicketEntity</class>
|
||||
|
||||
<!-- User Federation Storage -->
|
||||
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
|
@ -32,6 +33,7 @@ import org.keycloak.authorization.permission.evaluator.Evaluators;
|
|||
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
import org.keycloak.authorization.store.PolicyStore;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
|
@ -122,11 +124,69 @@ public final class AuthorizationProvider implements Provider {
|
|||
return storeFactoryDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered {@link PolicyProviderFactory}.
|
||||
*
|
||||
* @return a {@link List} containing all registered {@link PolicyProviderFactory}
|
||||
*/
|
||||
public Collection<PolicyProviderFactory> getProviderFactories() {
|
||||
return this.policyProviderFactories.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PolicyProviderFactory} given a <code>type</code>.
|
||||
*
|
||||
* @param type the type of the policy provider
|
||||
* @param <F> the expected type of the provider
|
||||
* @return a {@link PolicyProviderFactory} with the given <code>type</code>
|
||||
*/
|
||||
public <F extends PolicyProviderFactory> F getProviderFactory(String type) {
|
||||
return (F) policyProviderFactories.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PolicyProviderFactory} given a <code>type</code>.
|
||||
*
|
||||
* @param type the type of the policy provider
|
||||
* @param <P> the expected type of the provider
|
||||
* @return a {@link PolicyProvider} with the given <code>type</code>
|
||||
*/
|
||||
public <P extends PolicyProvider> P getProvider(String type) {
|
||||
PolicyProviderFactory policyProviderFactory = policyProviderFactories.get(type);
|
||||
|
||||
if (policyProviderFactory == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (P) policyProviderFactory.create(this);
|
||||
}
|
||||
|
||||
public KeycloakSession getKeycloakSession() {
|
||||
return this.keycloakSession;
|
||||
}
|
||||
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
private StoreFactory createStoreFactory(StoreFactory storeFactory) {
|
||||
return new StoreFactory() {
|
||||
|
||||
ResourceStore resourceStore;
|
||||
ScopeStore scopeStore;
|
||||
PolicyStore policyStore;
|
||||
|
||||
@Override
|
||||
public ResourceStore getResourceStore() {
|
||||
return storeFactory.getResourceStore();
|
||||
if (resourceStore == null) {
|
||||
resourceStore = createResourceStoreWrapper(storeFactory);
|
||||
}
|
||||
return resourceStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,23 +196,92 @@ public final class AuthorizationProvider implements Provider {
|
|||
|
||||
@Override
|
||||
public ScopeStore getScopeStore() {
|
||||
return storeFactory.getScopeStore();
|
||||
if (scopeStore == null) {
|
||||
scopeStore = createScopeWrapper(storeFactory);
|
||||
}
|
||||
return scopeStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolicyStore getPolicyStore() {
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
if (policyStore == null) {
|
||||
policyStore = createPolicyWrapper(storeFactory);
|
||||
}
|
||||
return policyStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionTicketStore getPermissionTicketStore() {
|
||||
return storeFactory.getPermissionTicketStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
storeFactory.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ScopeStore createScopeWrapper(StoreFactory storeFactory) {
|
||||
return new ScopeStore() {
|
||||
|
||||
ScopeStore delegate = storeFactory.getScopeStore();
|
||||
|
||||
@Override
|
||||
public Scope create(String name, ResourceServer resourceServer) {
|
||||
return delegate.create(name, resourceServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
Scope scope = findById(id, null);
|
||||
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
|
||||
List<PermissionTicket> permissions = ticketStore.findByScope(id, scope.getResourceServer().getId());
|
||||
|
||||
for (PermissionTicket permission : permissions) {
|
||||
ticketStore.delete(permission.getId());
|
||||
}
|
||||
|
||||
delegate.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope findById(String id, String resourceServerId) {
|
||||
return delegate.findById(id, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope findByName(String name, String resourceServerId) {
|
||||
return delegate.findByName(name, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Scope> findByResourceServer(String id) {
|
||||
return delegate.findByResourceServer(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Scope> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
|
||||
return delegate.findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PolicyStore createPolicyWrapper(StoreFactory storeFactory) {
|
||||
return new PolicyStore() {
|
||||
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
|
||||
@Override
|
||||
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
|
||||
Set<String> resources = representation.getResources();
|
||||
|
||||
if (resources != null) {
|
||||
representation.setResources(resources.stream().map(id -> {
|
||||
Resource resource = getResourceStore().findById(id, resourceServer.getId());
|
||||
Resource resource = storeFactory.getResourceStore().findById(id, resourceServer.getId());
|
||||
|
||||
if (resource == null) {
|
||||
resource = getResourceStore().findByName(id, resourceServer.getId());
|
||||
resource = storeFactory.getResourceStore().findByName(id, resourceServer.getId());
|
||||
}
|
||||
|
||||
if (resource == null) {
|
||||
|
@ -167,10 +296,10 @@ public final class AuthorizationProvider implements Provider {
|
|||
|
||||
if (scopes != null) {
|
||||
representation.setScopes(scopes.stream().map(id -> {
|
||||
Scope scope = getScopeStore().findById(id, resourceServer.getId());
|
||||
Scope scope = storeFactory.getScopeStore().findById(id, resourceServer.getId());
|
||||
|
||||
if (scope == null) {
|
||||
scope = getScopeStore().findByName(id, resourceServer.getId());
|
||||
scope = storeFactory.getScopeStore().findByName(id, resourceServer.getId());
|
||||
}
|
||||
|
||||
if (scope == null) {
|
||||
|
@ -186,10 +315,10 @@ public final class AuthorizationProvider implements Provider {
|
|||
|
||||
if (policies != null) {
|
||||
representation.setPolicies(policies.stream().map(id -> {
|
||||
Policy policy = getPolicyStore().findById(id, resourceServer.getId());
|
||||
Policy policy = storeFactory.getPolicyStore().findById(id, resourceServer.getId());
|
||||
|
||||
if (policy == null) {
|
||||
policy = getPolicyStore().findByName(id, resourceServer.getId());
|
||||
policy = storeFactory.getPolicyStore().findByName(id, resourceServer.getId());
|
||||
}
|
||||
|
||||
if (policy == null) {
|
||||
|
@ -268,60 +397,67 @@ public final class AuthorizationProvider implements Provider {
|
|||
};
|
||||
}
|
||||
|
||||
private ResourceStore createResourceStoreWrapper(StoreFactory storeFactory) {
|
||||
return new ResourceStore() {
|
||||
ResourceStore delegate = storeFactory.getResourceStore();
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
storeFactory.close();
|
||||
public Resource create(String name, ResourceServer resourceServer, String owner) {
|
||||
return delegate.create(name, resourceServer, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
Resource resource = findById(id, null);
|
||||
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
|
||||
List<PermissionTicket> permissions = ticketStore.findByResource(id, resource.getResourceServer().getId());
|
||||
|
||||
for (PermissionTicket permission : permissions) {
|
||||
ticketStore.delete(permission.getId());
|
||||
}
|
||||
|
||||
delegate.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource findById(String id, String resourceServerId) {
|
||||
return delegate.findById(id, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByOwner(String ownerId, String resourceServerId) {
|
||||
return delegate.findByOwner(ownerId, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByUri(String uri, String resourceServerId) {
|
||||
return delegate.findByUri(uri, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByResourceServer(String resourceServerId) {
|
||||
return delegate.findByResourceServer(resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
|
||||
return delegate.findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByScope(List<String> id, String resourceServerId) {
|
||||
return delegate.findByScope(id, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource findByName(String name, String resourceServerId) {
|
||||
return delegate.findByName(name, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByType(String type, String resourceServerId) {
|
||||
return delegate.findByType(type, resourceServerId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered {@link PolicyProviderFactory}.
|
||||
*
|
||||
* @return a {@link List} containing all registered {@link PolicyProviderFactory}
|
||||
*/
|
||||
public Collection<PolicyProviderFactory> getProviderFactories() {
|
||||
return this.policyProviderFactories.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PolicyProviderFactory} given a <code>type</code>.
|
||||
*
|
||||
* @param type the type of the policy provider
|
||||
* @param <F> the expected type of the provider
|
||||
* @return a {@link PolicyProviderFactory} with the given <code>type</code>
|
||||
*/
|
||||
public <F extends PolicyProviderFactory> F getProviderFactory(String type) {
|
||||
return (F) policyProviderFactories.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PolicyProviderFactory} given a <code>type</code>.
|
||||
*
|
||||
* @param type the type of the policy provider
|
||||
* @param <P> the expected type of the provider
|
||||
* @return a {@link PolicyProvider} with the given <code>type</code>
|
||||
*/
|
||||
public <P extends PolicyProvider> P getProvider(String type) {
|
||||
PolicyProviderFactory policyProviderFactory = policyProviderFactories.get(type);
|
||||
|
||||
if (policyProviderFactory == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (P) policyProviderFactory.create(this);
|
||||
}
|
||||
|
||||
public KeycloakSession getKeycloakSession() {
|
||||
return this.keycloakSession;
|
||||
}
|
||||
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2017 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.model;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PermissionTicket {
|
||||
|
||||
String ID = "id";
|
||||
String RESOURCE = "resource.id";
|
||||
String SCOPE = "scope.id";
|
||||
String SCOPE_IS_NULL = "scope_is_null";
|
||||
String OWNER = "owner";
|
||||
String GRANTED = "granted";
|
||||
String REQUESTER = "requester";
|
||||
String REQUESTER_IS_NULL = "requester_is_null";
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for this instance.
|
||||
*
|
||||
* @return the unique identifier for this instance
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns the resource's owner, which is usually an identifier that uniquely identifies the resource's owner.
|
||||
*
|
||||
* @return the owner of this resource
|
||||
*/
|
||||
String getOwner();
|
||||
|
||||
String getRequester();
|
||||
|
||||
/**
|
||||
* Returns the {@link Resource} associated with this instance
|
||||
*
|
||||
* @return the {@link Resource} associated with this instance
|
||||
*/
|
||||
Resource getResource();
|
||||
|
||||
/**
|
||||
* Returns the {@link Scope} associated with this instance
|
||||
*
|
||||
* @return the {@link Scope} associated with this instance
|
||||
*/
|
||||
Scope getScope();
|
||||
|
||||
boolean isGranted();
|
||||
|
||||
Long getCreatedTimestamp();
|
||||
|
||||
Long getGrantedTimestamp();
|
||||
void setGrantedTimestamp(Long millis);
|
||||
|
||||
/**
|
||||
* Returns the {@link ResourceServer} where this policy belongs to.
|
||||
*
|
||||
* @return a resource server
|
||||
*/
|
||||
ResourceServer getResourceServer();
|
||||
}
|
|
@ -49,6 +49,20 @@ public interface Resource {
|
|||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Returns the end user friendly name for this resource. If not defined, value for {@link #getName()} is returned.
|
||||
*
|
||||
* @return the friendly name for this resource
|
||||
*/
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* Sets an end user friendly name for this resource.
|
||||
*
|
||||
* @param name the name of this resource
|
||||
*/
|
||||
void setDisplayName(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link java.net.URI} that uniquely identify this resource.
|
||||
*
|
||||
|
@ -112,5 +126,8 @@ public interface Resource {
|
|||
*/
|
||||
String getOwner();
|
||||
|
||||
boolean isOwnerManagedAccess();
|
||||
void setOwnerManagedAccess(boolean ownerManagedAccess);
|
||||
|
||||
void updateScopes(Set<Scope> scopes);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,20 @@ public interface Scope {
|
|||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Returns the end user friendly name for this scope. If not defined, value for {@link #getName()} is returned.
|
||||
*
|
||||
* @return the friendly name for this scope
|
||||
*/
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* Sets an end user friendly name for this scope.
|
||||
*
|
||||
* @param name the name of this scope
|
||||
*/
|
||||
void setDisplayName(String name);
|
||||
|
||||
/**
|
||||
* Returns an icon {@link java.net.URI} for this scope.
|
||||
*
|
||||
|
|
|
@ -44,7 +44,7 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(Decision decision) {
|
||||
public Decision evaluate(Decision decision) {
|
||||
try {
|
||||
while (this.permissions.hasNext()) {
|
||||
this.policyEvaluator.evaluate(this.permissions.next(), this.executionContext, decision);
|
||||
|
@ -53,6 +53,7 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
|
|||
} catch (Throwable cause) {
|
||||
decision.onError(cause);
|
||||
}
|
||||
return decision;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,6 +30,6 @@ import org.keycloak.authorization.policy.evaluation.Result;
|
|||
*/
|
||||
public interface PermissionEvaluator {
|
||||
|
||||
void evaluate(Decision decision);
|
||||
<D extends Decision> D evaluate(D decision);
|
||||
List<Result> evaluate();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue