/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.testsuite.authorization; import org.apache.commons.collections.map.HashedMap; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.junit.Before; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision; import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DefaultEvaluation; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.common.ClientConnection; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.util.JsonSerialization; import javax.ws.rs.client.Invocation; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest { protected ResourceServer resourceServer; protected Resource adminResource; protected Policy anyAdminPolicy; protected Policy onlyFromSpecificAddressPolicy; protected Policy administrationPolicy; protected Resource albumResource; protected Policy anyUserPolicy; @Before public void onBefore() { super.onBefore(); this.resourceServer = createResourceServer(); this.adminResource = createAdminAlbumResource(); this.anyAdminPolicy = createAnyAdminPolicy(); this.onlyFromSpecificAddressPolicy = createOnlyFromSpecificAddressPolicy(); this.administrationPolicy = createAdministrationPolicy(); this.albumResource = createAlbumResource(); this.anyUserPolicy = createAnyUserPolicy(); } protected ResourceServer createResourceServer() { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); ResourceServer resourceServer = resourceServerStore.create(getClientByClientId("photoz-restful-api").getId()); resourceServer.setAllowRemoteResourceManagement(true); return resourceServer; }); } protected Map<String, DefaultEvaluation> performEvaluation(List<ResourcePermission> permissions, AccessToken accessToken, ClientConnection clientConnection) { Map<String, DefaultEvaluation> evaluations = new HashedMap(); onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); // during tests we create resource instances, but we need to reload them to get their collections updated List<ResourcePermission> updatedPermissions = permissions.stream().map(permission -> { Resource resource = storeFactory.getResourceStore().findById(permission.getResource().getId(), resourceServer.getId()); return new ResourcePermission(resource, permission.getScopes(), permission.getResourceServer()); }).collect(Collectors.toList()); authorizationProvider.evaluators().from(updatedPermissions, createEvaluationContext(accessToken, clientConnection, authorizationProvider)).evaluate(new Decision<DefaultEvaluation>() { @Override public void onDecision(DefaultEvaluation evaluation) { evaluations.put(evaluation.getPolicy().getId(), evaluation); } @Override public void onError(Throwable cause) { throw new RuntimeException("Permission evaluation failed.", cause); } }); }); return evaluations; } private KeycloakEvaluationContext createEvaluationContext(AccessToken accessToken, ClientConnection clientConnection, AuthorizationProvider authorizationProvider) { KeycloakSession keycloakSession = authorizationProvider.getKeycloakSession(); keycloakSession.getContext().setConnection(clientConnection); keycloakSession.getContext().setClient(getClientByClientId("photoz-html5-client")); ResteasyProviderFactory.pushContext(HttpHeaders.class, createHttpHeaders()); KeycloakIdentity identity = new KeycloakIdentity(accessToken, keycloakSession); return new KeycloakEvaluationContext(identity, keycloakSession); } protected AccessToken createAccessToken(Set<String> roles) { AccessToken accessToken = new AccessToken(); accessToken.setRealmAccess(new AccessToken.Access()); accessToken.getRealmAccess().roles(roles); return accessToken; } private HttpHeaders createHttpHeaders() { return new HttpHeaders() { @Override public List<String> getRequestHeader(String name) { return null; } @Override public String getHeaderString(String name) { return null; } @Override public MultivaluedMap<String, String> getRequestHeaders() { return null; } @Override public List<MediaType> getAcceptableMediaTypes() { return null; } @Override public List<Locale> getAcceptableLanguages() { return null; } @Override public MediaType getMediaType() { return null; } @Override public Locale getLanguage() { return null; } @Override public Map<String, Cookie> getCookies() { return null; } @Override public Date getDate() { return null; } @Override public int getLength() { return 0; } }; } protected ClientConnection createClientConnection(String remoteAddr) { return new ClientConnection() { @Override public String getRemoteAddr() { return remoteAddr; } @Override public String getRemoteHost() { return "localhost"; } @Override public int getRemotePort() { return 0; } @Override public String getLocalAddr() { return null; } @Override public int getLocalPort() { return 0; } }; } protected Invocation.Builder newPermissionRequest(String... id) { String idPathParam = ""; if (id.length != 0) { idPathParam = "/" + id[0]; } return newClient(getClientByClientId("photoz-restful-api"), "/resource-server/policy" + idPathParam); } private Policy createAdministrationPolicy() { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); PolicyRepresentation representation = new PolicyRepresentation(); representation.setName("Administration Policy"); representation.setType("aggregate"); representation.addPolicy(anyAdminPolicy.getName()); representation.addPolicy(onlyFromSpecificAddressPolicy.getName()); Policy policy = policyStore.create(representation, resourceServer); return policy; }); } private Policy createOnlyFromSpecificAddressPolicy() { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); PolicyRepresentation representation = new PolicyRepresentation(); representation.setName("Only From a Specific Client Address"); representation.setType("js"); HashedMap config = new HashedMap(); config.put("code", "var contextAttributes = $evaluation.getContext().getAttributes();" + "var networkAddress = contextAttributes.getValue('kc.client.network.ip_address');" + "if ('127.0.0.1'.equals(networkAddress.asInetAddress(0).getHostAddress())) {" + "$evaluation.grant();" + "}"); representation.setConfig(config); return policyStore.create(representation, resourceServer); }); } private Policy createAnyAdminPolicy() { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); RolePolicyRepresentation representation = new RolePolicyRepresentation(); representation.setName("Any Admin Policy"); representation.setType("role"); representation.addRole("admin", false); return policyStore.create(representation, resourceServer); }); } private Resource createAdminAlbumResource() { ResourceRepresentation representation = new ResourceRepresentation(); representation.setName("Admin Resources"); representation.setType("http://photoz.com/admin"); representation.setUri("/admin/*"); HashSet<ScopeRepresentation> scopes = new HashSet<>(); scopes.add(new ScopeRepresentation("urn:photoz.com:scopes:album:admin:manage")); representation.setScopes(scopes); return createResource(representation); } private Resource createAlbumResource() { ResourceRepresentation representation = new ResourceRepresentation(); representation.setName("Album Resource"); representation.setType("http://photoz.com/album"); representation.setUri("/album/*"); HashSet<ScopeRepresentation> scopes = new HashSet<>(); scopes.add(new ScopeRepresentation("urn:photoz.com:scopes:album:view")); scopes.add(new ScopeRepresentation("urn:photoz.com:scopes:album:create")); scopes.add(new ScopeRepresentation("urn:photoz.com:scopes:album:delete")); representation.setScopes(scopes); return createResource(representation); } protected Resource createResource(ResourceRepresentation representation) { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); ScopeStore scopeStore = storeFactory.getScopeStore(); representation.getScopes().forEach(scopeRepresentation -> { scopeStore.create(scopeRepresentation.getName(), resourceServer); }); ResourceStore resourceStore = storeFactory.getResourceStore(); Resource albumResource = resourceStore.create(representation.getName(), resourceServer, resourceServer.getId()); albumResource.setType(representation.getType()); albumResource.setUri(representation.getUri()); albumResource.setIconUri(representation.getIconUri()); return albumResource; }); } private Policy createAnyUserPolicy() { return onAuthorizationSession(authorizationProvider -> { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); PolicyRepresentation representation = new PolicyRepresentation(); representation.setName("Any User Policy"); representation.setType("role"); HashedMap config = new HashedMap(); RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME); RoleModel userRole = realm.getRole("user"); Map role = new HashMap(); role.put("id", userRole.getId()); try { config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role})); } catch (IOException e) { throw new RuntimeException(e); } representation.setConfig(config); Policy policy = policyStore.create(representation, resourceServer); return policy; }); } }