[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:
Pedro Igor 2018-02-28 04:53:10 -03:00 committed by Stian Thorgersen
parent 190ad06f1a
commit 91bdc4bde2
205 changed files with 8661 additions and 3389 deletions

View file

@ -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);

View file

@ -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,54 +64,58 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true);
}
Request request = httpFacade.getRequest();
String path = getPath(request);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
if (securityContext != null) {
AccessToken accessToken = securityContext.getToken();
if (securityContext == null) {
if (pathConfig != null) {
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
}
return createEmptyAuthorizationContext(false);
}
if (accessToken != null) {
Request request = httpFacade.getRequest();
String path = getPath(request);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
AccessToken accessToken = securityContext.getToken();
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (accessToken != null) {
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
return createEmptyAuthorizationContext(false);
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
}
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
LOGGER.debugf("Could not find a configuration for path [%s]", path);
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return createAuthorizationContext(accessToken, null);
}
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
handleAccessDenied(httpFacade);
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
}
return createEmptyAuthorizationContext(false);
}
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
}
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
}
}
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
if (!challenge(pathConfig, methodConfig, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
}
if (!challenge(pathConfig, methodConfig, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
}
}
@ -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());
}
}

View file

@ -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) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
return permission.forResource(permissionRequest).getTicket();
if (getEnforcerConfig().getUserManagedAccess() != null) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceId(pathConfig.getId());
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
return permission.create(permissionRequest).getTicket();
}
return null;
}
}

View file

@ -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();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceId(pathConfig.getId());
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
AccessToken accessToken = securityContext.getToken();
AuthorizationRequest authzRequest;
if (getEnforcerConfig().getUserManagedAccess() != null) {
LOGGER.debug("Obtaining authorization for authenticated user.");
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(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);
if (authzResponse != null) {
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
return null;
PermissionResponse permissionResponse = authzClient.protection().permission().create(permissionRequest);
authzRequest = new AuthorizationRequest();
authzRequest.setTicket(permissionResponse.getTicket());
} 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);
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.getToken(), deployment);
}
return null;
} catch (AuthorizationDeniedException e) {
LOGGER.debug("Authorization denied", e);
return null;

View file

@ -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());

View file

@ -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);

View file

@ -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,68 +47,61 @@
* 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(',');
if (authorizationRequest && authorizationRequest.ticket) {
var request = new XMLHttpRequest();
for (i = 0; i < params.length; i++) {
var param = params[i].split('=');
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
if (param[0] == 'ticket') {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
request.open('POST', _instance.config.rpt_endpoint, true);
request.setRequestHeader('Content-Type', 'application/json')
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).rpt;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
};
var ticket = param[1].substring(1, param[1].length - 1).trim();
request.send(JSON.stringify(
{
ticket: ticket,
rpt: _instance.rpt
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
));
}
}
};
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
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;

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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,29 +59,34 @@ public class Configuration extends AdapterConfig {
this.httpClient = httpClient;
}
@JsonIgnore
private ClientAuthenticator clientAuthenticator = new ClientAuthenticator() {
@Override
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
String secret = (String) getCredentials().get("secret");
if (secret == null) {
throw new RuntimeException("Client secret not provided.");
}
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(getResource(), secret));
}
};
public HttpClient getHttpClient() {
if (this.httpClient == null) {
this.httpClient = HttpClients.createDefault();
}
return httpClient;
}
public ClientAuthenticator getClientAuthenticator() {
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(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
String secret = (String) getCredentials().get("secret");
if (secret == null) {
throw new RuntimeException("Client secret not provided.");
}
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(getResource(), secret));
}
};
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<>();

View file

@ -1,13 +1,12 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -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;
}
}

View file

@ -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>
*/

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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) {
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 this.http.<PermissionResponse>post("/authz/protection/permission")
.authorizationBearer(this.pat.call())
.json(JsonSerialization.writeValueAsBytes(request))
.response().json(PermissionResponse.class).execute();
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);
}
}
}

View file

@ -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) {
/**
* 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(ResourceRepresentation.class).execute();
}
};
try {
return this.http.<RegistrationResponse>post("/authz/protection/resource_set")
.authorizationBearer(this.pat.call())
.json(JsonSerialization.writeValueAsBytes(resource))
.response().json(RegistrationResponse.class).execute();
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) {
/**
* 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 {
this.http.<RegistrationResponse>put("/authz/protection/resource_set/" + resource.getId())
.authorizationBearer(this.pat.call())
.json(JsonSerialization.writeValueAsBytes(resource)).execute();
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) {
/**
* 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 {
this.http.delete("/authz/protection/resource_set/" + id)
.authorizationBearer(this.pat.call())
.execute();
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;
}
}

View file

@ -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()

View file

@ -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) {

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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);
}
}
});
}
};
}
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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";
}

View file

@ -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 {
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -1,13 +1,12 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -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>

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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>
<%
}

View file

@ -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();
}
}

View file

@ -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"

View file

@ -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/>

View file

@ -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.';

View file

@ -34,6 +34,10 @@
keycloak.logout();
};
this.account = function () {
keycloak.accountManagement();
}
this.hasRole = function (name) {
if (keycloak && keycloak.hasRealmRole(name)) {
return true;

View file

@ -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

View file

@ -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>
<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>
</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>

View file

@ -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"]
}

View file

@ -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);
}
this.entityManager.persist(newAlbum);
createProtectedResource(newAlbum);
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);
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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"
}
]
}

View file

@ -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": {
"user-managed-access" : {},
"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"
},

View file

@ -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": {

View file

@ -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>
<%

View file

@ -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>

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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());
}
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -1,13 +1,12 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
@Override
public String getScopeId() {
return scopeId;
}
public int getLimit() {
return limit;
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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));
}

View file

@ -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);
query.setParameter("serverId", resourceServerId);
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();
predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId));
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() + "%"));
}

View file

@ -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() {

View file

@ -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());
}
}
}

View file

@ -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<>();

View file

@ -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();

View file

@ -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();

View file

@ -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();
}
}

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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,159 +124,6 @@ public final class AuthorizationProvider implements Provider {
return storeFactoryDelegate;
}
private StoreFactory createStoreFactory(StoreFactory storeFactory) {
return new StoreFactory() {
@Override
public ResourceStore getResourceStore() {
return storeFactory.getResourceStore();
}
@Override
public ResourceServerStore getResourceServerStore() {
return storeFactory.getResourceServerStore();
}
@Override
public ScopeStore getScopeStore() {
return storeFactory.getScopeStore();
}
@Override
public PolicyStore getPolicyStore() {
PolicyStore policyStore = storeFactory.getPolicyStore();
return new PolicyStore() {
@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());
if (resource == null) {
resource = getResourceStore().findByName(id, resourceServer.getId());
}
if (resource == null) {
throw new RuntimeException("Resource [" + id + "] does not exist");
}
return resource.getId();
}).collect(Collectors.toSet()));
}
Set<String> scopes = representation.getScopes();
if (scopes != null) {
representation.setScopes(scopes.stream().map(id -> {
Scope scope = getScopeStore().findById(id, resourceServer.getId());
if (scope == null) {
scope = getScopeStore().findByName(id, resourceServer.getId());
}
if (scope == null) {
throw new RuntimeException("Scope [" + id + "] does not exist");
}
return scope.getId();
}).collect(Collectors.toSet()));
}
Set<String> policies = representation.getPolicies();
if (policies != null) {
representation.setPolicies(policies.stream().map(id -> {
Policy policy = getPolicyStore().findById(id, resourceServer.getId());
if (policy == null) {
policy = getPolicyStore().findByName(id, resourceServer.getId());
}
if (policy == null) {
throw new RuntimeException("Policy [" + id + "] does not exist");
}
return policy.getId();
}).collect(Collectors.toSet()));
}
return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer));
}
@Override
public void delete(String id) {
Policy policy = findById(id, null);
if (policy != null) {
ResourceServer resourceServer = policy.getResourceServer();
findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> {
dependentPolicy.removeAssociatedPolicy(policy);
if (dependentPolicy.getAssociatedPolicies().isEmpty()) {
delete(dependentPolicy.getId());
}
});
policyStore.delete(id);
}
}
@Override
public Policy findById(String id, String resourceServerId) {
return policyStore.findById(id, resourceServerId);
}
@Override
public Policy findByName(String name, String resourceServerId) {
return policyStore.findByName(name, resourceServerId);
}
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
return policyStore.findByResourceServer(resourceServerId);
}
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return policyStore.findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<Policy> findByResource(String resourceId, String resourceServerId) {
return policyStore.findByResource(resourceId, resourceServerId);
}
@Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
return policyStore.findByResourceType(resourceType, resourceServerId);
}
@Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
return policyStore.findByScopeIds(scopeIds, resourceServerId);
}
@Override
public List<Policy> findByType(String type, String resourceServerId) {
return policyStore.findByType(type, resourceServerId);
}
@Override
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
return policyStore.findDependentPolicies(id, resourceServerId);
}
};
}
@Override
public void close() {
storeFactory.close();
}
};
}
/**
* Returns the registered {@link PolicyProviderFactory}.
*
@ -324,4 +173,291 @@ public final class AuthorizationProvider implements Provider {
public void close() {
}
private StoreFactory createStoreFactory(StoreFactory storeFactory) {
return new StoreFactory() {
ResourceStore resourceStore;
ScopeStore scopeStore;
PolicyStore policyStore;
@Override
public ResourceStore getResourceStore() {
if (resourceStore == null) {
resourceStore = createResourceStoreWrapper(storeFactory);
}
return resourceStore;
}
@Override
public ResourceServerStore getResourceServerStore() {
return storeFactory.getResourceServerStore();
}
@Override
public ScopeStore getScopeStore() {
if (scopeStore == null) {
scopeStore = createScopeWrapper(storeFactory);
}
return scopeStore;
}
@Override
public PolicyStore 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 = storeFactory.getResourceStore().findById(id, resourceServer.getId());
if (resource == null) {
resource = storeFactory.getResourceStore().findByName(id, resourceServer.getId());
}
if (resource == null) {
throw new RuntimeException("Resource [" + id + "] does not exist");
}
return resource.getId();
}).collect(Collectors.toSet()));
}
Set<String> scopes = representation.getScopes();
if (scopes != null) {
representation.setScopes(scopes.stream().map(id -> {
Scope scope = storeFactory.getScopeStore().findById(id, resourceServer.getId());
if (scope == null) {
scope = storeFactory.getScopeStore().findByName(id, resourceServer.getId());
}
if (scope == null) {
throw new RuntimeException("Scope [" + id + "] does not exist");
}
return scope.getId();
}).collect(Collectors.toSet()));
}
Set<String> policies = representation.getPolicies();
if (policies != null) {
representation.setPolicies(policies.stream().map(id -> {
Policy policy = storeFactory.getPolicyStore().findById(id, resourceServer.getId());
if (policy == null) {
policy = storeFactory.getPolicyStore().findByName(id, resourceServer.getId());
}
if (policy == null) {
throw new RuntimeException("Policy [" + id + "] does not exist");
}
return policy.getId();
}).collect(Collectors.toSet()));
}
return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer));
}
@Override
public void delete(String id) {
Policy policy = findById(id, null);
if (policy != null) {
ResourceServer resourceServer = policy.getResourceServer();
findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> {
dependentPolicy.removeAssociatedPolicy(policy);
if (dependentPolicy.getAssociatedPolicies().isEmpty()) {
delete(dependentPolicy.getId());
}
});
policyStore.delete(id);
}
}
@Override
public Policy findById(String id, String resourceServerId) {
return policyStore.findById(id, resourceServerId);
}
@Override
public Policy findByName(String name, String resourceServerId) {
return policyStore.findByName(name, resourceServerId);
}
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
return policyStore.findByResourceServer(resourceServerId);
}
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return policyStore.findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<Policy> findByResource(String resourceId, String resourceServerId) {
return policyStore.findByResource(resourceId, resourceServerId);
}
@Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
return policyStore.findByResourceType(resourceType, resourceServerId);
}
@Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
return policyStore.findByScopeIds(scopeIds, resourceServerId);
}
@Override
public List<Policy> findByType(String type, String resourceServerId) {
return policyStore.findByType(type, resourceServerId);
}
@Override
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
return policyStore.findDependentPolicies(id, resourceServerId);
}
};
}
private ResourceStore createResourceStoreWrapper(StoreFactory storeFactory) {
return new ResourceStore() {
ResourceStore delegate = storeFactory.getResourceStore();
@Override
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);
}
};
}
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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.
*

View file

@ -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

View file

@ -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