/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual 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.util; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.identity.Identity; 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.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.representations.idm.authorization.Permission; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ public final class Permissions { /** * Returns a list of permissions for all resources and scopes that belong to the given <code>resourceServer</code> and * <code>identity</code>. * * TODO: review once we support caches * * @param resourceServer * @param identity * @param authorization * @return */ public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) { List<ResourcePermission> permissions = new ArrayList<>(); StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); resourceStore.findByOwner(resourceServer.getClientId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization))); resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization))); return permissions; } public static List<ResourcePermission> createResourcePermissions(Resource resource, Set<String> requestedScopes, AuthorizationProvider authorization) { List<ResourcePermission> permissions = new ArrayList<>(); String type = resource.getType(); ResourceServer resourceServer = resource.getResourceServer(); List<Scope> scopes; if (requestedScopes.isEmpty()) { scopes = new LinkedList<>(resource.getScopes()); // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource // is owned by the resource server itself if (type != null && !resource.getOwner().equals(resourceServer.getClientId())) { StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); resourceStore.findByType(type, resourceServer.getId()).forEach(resource1 -> { if (resource1.getOwner().equals(resourceServer.getClientId())) { for (Scope typeScope : resource1.getScopes()) { if (!scopes.contains(typeScope)) { scopes.add(typeScope); } } } }); } } else { ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); scopes = requestedScopes.stream().map(scopeName -> { Scope byName = scopeStore.findByName(scopeName, resource.getResourceServer().getId()); if (byName == null) { throw new RuntimeException("Invalid scope [" + scopeName + "]."); } return byName; }).collect(Collectors.toList()); } permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer())); return permissions; } public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization) { List<ResourcePermission> permissions = new ArrayList<>(); String type = resource.getType(); ResourceServer resourceServer = resource.getResourceServer(); // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource // is owned by the resource server itself if (type != null && !resource.getOwner().equals(resourceServer.getClientId())) { StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); resourceStore.findByType(type, resourceServer.getId()).forEach(resource1 -> { if (resource1.getOwner().equals(resourceServer.getClientId())) { for (Scope typeScope : resource1.getScopes()) { if (!scopes.contains(typeScope)) { scopes.add(typeScope); } } } }); } permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer())); return permissions; } public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServerId) { Map<String, Permission> permissions = new HashMap<>(); for (Result result : evaluation) { Set<Scope> deniedScopes = new HashSet<>(); Set<Scope> grantedScopes = new HashSet<>(); boolean resourceDenied = false; ResourcePermission permission = result.getPermission(); List<Result.PolicyResult> results = result.getResults(); int deniedCount = results.size(); for (Result.PolicyResult policyResult : results) { Policy policy = policyResult.getPolicy(); Set<Scope> policyScopes = policy.getScopes(); if (Effect.PERMIT.equals(policyResult.getStatus())) { if (isScopePermission(policy)) { // try to grant any scope from a scope-based permission grantedScopes.addAll(policyScopes); } else if (isResourcePermission(policy)) { // we assume that all requested scopes should be granted given that we are processing a resource-based permission. // Later they will be filtered based on any denied scope, if any. // TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource. grantedScopes.addAll(permission.getScopes()); } deniedCount--; } else { if (isScopePermission(policy)) { // store all scopes associated with the scope-based permission deniedScopes.addAll(policyScopes); } else if (isResourcePermission(policy)) { // we should not grant anything resourceDenied = true; break; } } } if (!resourceDenied) { // remove any scope denied from the list of granted scopes if (!deniedScopes.isEmpty()) { grantedScopes.removeAll(deniedScopes); } // if there are no policy results is because the permission didn't match any policy. // In this case, if results is empty is because we are in permissive mode. if (!results.isEmpty()) { // update the current permission with the granted scopes permission.getScopes().clear(); permission.getScopes().addAll(grantedScopes); } if (deniedCount == 0) { result.setStatus(Effect.PERMIT); grantPermission(authorizationProvider, permissions, permission, resourceServerId); } else { // if a full deny or resource denied or the requested scopes were denied if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) { result.setStatus(Effect.DENY); } else { result.setStatus(Effect.PERMIT); grantPermission(authorizationProvider, permissions, permission, resourceServerId); } } } } return permissions.values().stream().collect(Collectors.toList()); } private static boolean isResourcePermission(Policy policy) { return "resource".equals(policy.getType()); } private static boolean isScopePermission(Policy policy) { return "scope".equals(policy.getType()); } private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, String resourceServer) { List<Resource> resources = new ArrayList<>(); Resource resource = permission.getResource(); Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()); if (resource != null) { resources.add(resource); } else { List<Scope> permissionScopes = permission.getScopes(); if (!permissionScopes.isEmpty()) { ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer)); } } if (!resources.isEmpty()) { for (Resource allowedResource : resources) { String resourceId = allowedResource.getId(); String resourceName = allowedResource.getName(); Permission evalPermission = permissions.get(allowedResource.getId()); if (evalPermission == null) { evalPermission = new Permission(resourceId, resourceName, scopes); permissions.put(resourceId, evalPermission); } if (scopes != null && !scopes.isEmpty()) { Set<String> finalScopes = evalPermission.getScopes(); if (finalScopes == null) { finalScopes = new HashSet(); evalPermission.setScopes(finalScopes); } for (String scopeName : scopes) { if (!finalScopes.contains(scopeName)) { finalScopes.add(scopeName); } } } } } else { Permission scopePermission = new Permission(null, null, scopes); permissions.put(scopePermission.toString(), scopePermission); } } }