Java tutorial
/** * Copyright (c) 2014-2015 Evolveum * * 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 com.evolveum.midpoint.security.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.mutable.MutableBoolean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.Visitable; import com.evolveum.midpoint.prism.Visitor; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.parser.QueryConvertor; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.AllFilter; import com.evolveum.midpoint.prism.query.AndFilter; import com.evolveum.midpoint.prism.query.InOidFilter; import com.evolveum.midpoint.prism.query.NoneFilter; import com.evolveum.midpoint.prism.query.NotFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.OrgFilter; import com.evolveum.midpoint.prism.query.QueryJaxbConvertor; import com.evolveum.midpoint.prism.query.TypeFilter; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; import com.evolveum.midpoint.security.api.Authorization; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.security.api.ObjectSecurityConstraints; import com.evolveum.midpoint.security.api.OwnerResolver; import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.security.api.UserProfileService; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.AuthorizationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationDecisionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSpecificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OwnedObjectSpecificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SpecialObjectSpecificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; /** * @author Radovan Semancik * */ @Component("securityEnforcer") public class SecurityEnforcerImpl implements SecurityEnforcer { private static final Trace LOGGER = TraceManager.getTrace(SecurityEnforcerImpl.class); @Autowired(required = true) @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired(required = true) private MatchingRuleRegistry matchingRuleRegistry; @Autowired(required = true) private PrismContext prismContext; private UserProfileService userProfileService = null; @Override public UserProfileService getUserProfileService() { return userProfileService; } @Override public void setUserProfileService(UserProfileService userProfileService) { this.userProfileService = userProfileService; } @Override public MidPointPrincipal getPrincipal() throws SecurityViolationException { return SecurityUtil.getPrincipal(); } @Override public boolean isAuthenticated() { return SecurityUtil.isAuthenticated(); } @Override public void setupPreAuthenticatedSecurityContext(Authentication authentication) { SecurityContext securityContext = SecurityContextHolder.getContext(); securityContext.setAuthentication(authentication); } @Override public void setupPreAuthenticatedSecurityContext(PrismObject<UserType> user) { MidPointPrincipal principal; if (userProfileService == null) { LOGGER.warn("No user profile service set up in SecurityEnforcer. " + "This is OK in low-level tests but it is a serious problem in running system"); principal = new MidPointPrincipal(user.asObjectable()); } else { principal = userProfileService.getPrincipal(user); } Authentication authentication = new PreAuthenticatedAuthenticationToken(principal, null); setupPreAuthenticatedSecurityContext(authentication); } @Override public <O extends ObjectType, T extends ObjectType> boolean isAuthorized(String operationUrl, AuthorizationPhaseType phase, PrismObject<O> object, ObjectDelta<O> delta, PrismObject<T> target, OwnerResolver ownerResolver) throws SchemaException { MidPointPrincipal midPointPrincipal = getMidPointPrincipal(); if (midPointPrincipal == null) { // No need to log, the getMidPointPrincipal() already logs the reason return false; } if (phase == null) { if (!isAuthorizedInternal(midPointPrincipal, operationUrl, AuthorizationPhaseType.REQUEST, object, delta, target, ownerResolver)) { return false; } return isAuthorizedInternal(midPointPrincipal, operationUrl, AuthorizationPhaseType.EXECUTION, object, delta, target, ownerResolver); } else { return isAuthorizedInternal(midPointPrincipal, operationUrl, phase, object, delta, target, ownerResolver); } } private <O extends ObjectType, T extends ObjectType> boolean isAuthorizedInternal( MidPointPrincipal midPointPrincipal, String operationUrl, AuthorizationPhaseType phase, PrismObject<O> object, ObjectDelta<O> delta, PrismObject<T> target, OwnerResolver ownerResolver) throws SchemaException { if (AuthorizationConstants.AUTZ_NO_ACCESS_URL.equals(operationUrl)) { return false; } if (phase == null) { throw new IllegalArgumentException("No phase"); } boolean allow = false; LOGGER.trace("AUTZ: evaluating authorization principal={}, op={}, phase={}, object={}, delta={}, target={}", new Object[] { midPointPrincipal, operationUrl, phase, object, delta, target }); final Collection<ItemPath> allowedItems = new ArrayList<>(); Collection<Authorization> authorities = midPointPrincipal.getAuthorities(); if (authorities != null) { for (GrantedAuthority authority : authorities) { if (authority instanceof Authorization) { Authorization autz = (Authorization) authority; String autzHumanReadableDesc = autz.getHumanReadableDesc(); LOGGER.trace("Evaluating {}", autzHumanReadableDesc); // First check if the authorization is applicable. // action if (!autz.getAction().contains(operationUrl) && !autz.getAction().contains(AuthorizationConstants.AUTZ_ALL_URL)) { LOGGER.trace(" {} not applicable for operation {}", autzHumanReadableDesc, operationUrl); continue; } // phase if (autz.getPhase() == null) { LOGGER.trace(" {} is applicable for all phases (continuing evaluation)", autzHumanReadableDesc); } else { if (autz.getPhase() != phase) { LOGGER.trace(" {} is not applicable for phases {} (breaking evaluation)", autzHumanReadableDesc, phase); continue; } else { LOGGER.trace(" {} is applicable for phases {} (continuing evaluation)", autzHumanReadableDesc, phase); } } // object if (isApplicable(autz.getObject(), object, midPointPrincipal, ownerResolver, "object", autzHumanReadableDesc)) { LOGGER.trace(" {} applicable for object {} (continuing evaluation)", autzHumanReadableDesc, object); } else { LOGGER.trace( " {} not applicable for object {}, none of the object specifications match (breaking evaluation)", autzHumanReadableDesc, object); continue; } // target if (isApplicable(autz.getTarget(), target, midPointPrincipal, ownerResolver, "target", autzHumanReadableDesc)) { LOGGER.trace(" {} applicable for target {} (continuing evaluation)", autzHumanReadableDesc, object); } else { LOGGER.trace( " {} not applicable for target {}, none of the target specifications match (breaking evaluation)", autzHumanReadableDesc, object); continue; } // authority is applicable to this situation. now we can process the decision. AuthorizationDecisionType decision = autz.getDecision(); if (decision == null || decision == AuthorizationDecisionType.ALLOW) { // if there is more than one role which specify // different authz (e.g one role specify allow for whole // objet, the other role specify allow only for some // attributes. this ended with allow for whole object (MID-2018) Collection<ItemPath> allowed = getItems(autz); if (allow && allowedItems.isEmpty()) { LOGGER.trace(" {}: ALLOW operation {} (but continue evaluation)", autzHumanReadableDesc, operationUrl); } else if (allow && allowed.isEmpty()) { allowedItems.clear(); } else { allowedItems.addAll(allowed); } LOGGER.trace(" {}: ALLOW operation {} (but continue evaluation)", autzHumanReadableDesc, operationUrl); allow = true; // Do NOT break here. Other authorization statements may still deny the operation } else { // item if (isApplicableItem(autz, object, delta)) { LOGGER.trace(" {}: Deny authorization applicable for items (continuing evaluation)", autzHumanReadableDesc); } else { LOGGER.trace(" {} not applicable for items (breaking evaluation)", autzHumanReadableDesc); continue; } LOGGER.trace(" {}: DENY operation {}", autzHumanReadableDesc, operationUrl); allow = false; // Break right here. Deny cannot be overridden by allow. This decision cannot be changed. break; } } else { LOGGER.warn("Unknown authority type {} in user {}", authority.getClass(), midPointPrincipal.getUsername()); } } } if (allow) { // Still check allowedItems. We may still deny the operation. if (allowedItems.isEmpty()) { // This means all items are allowed. No need to check anything LOGGER.trace(" Empty list of allowed items, operation allowed"); } else { // all items in the object and delta must be allowed final MutableBoolean itemDecision = new MutableBoolean(true); if (delta != null) { // If there is delta then consider only the delta. Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable) { ItemPath itemPath = getPath(visitable); if (itemPath != null && !itemPath.isEmpty()) { if (!isInList(itemPath, allowedItems)) { LOGGER.trace(" DENY operation because item {} in the delta is not allowed", itemPath); itemDecision.setValue(false); } } } }; delta.accept(visitor); } else if (object != null) { Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable) { ItemPath itemPath = getPath(visitable); if (itemPath != null && !itemPath.isEmpty()) { if (!isInList(itemPath, allowedItems)) { LOGGER.trace(" DENY operation because item {} in the object is not allowed", itemPath); itemDecision.setValue(false); } } } }; object.accept(visitor); } allow = itemDecision.booleanValue(); } } if (LOGGER.isTraceEnabled()) { LOGGER.trace("AUTZ result: principal={}, operation={}: {}", new Object[] { midPointPrincipal, operationUrl, allow }); } return allow; } private ItemPath getPath(Visitable visitable) { if (visitable instanceof ItemDelta) { return ((ItemDelta) visitable).getPath(); } else if (visitable instanceof Item) { return ((Item) visitable).getPath(); } else { return null; } } private boolean isInList(ItemPath itemPath, Collection<ItemPath> allowedItems) { boolean itemAllowed = false; for (ItemPath allowedPath : allowedItems) { if (allowedPath.isSubPathOrEquivalent(itemPath)) { itemAllowed = true; break; } } return itemAllowed; } @Override public <O extends ObjectType, T extends ObjectType> void authorize(String operationUrl, AuthorizationPhaseType phase, PrismObject<O> object, ObjectDelta<O> delta, PrismObject<T> target, OwnerResolver ownerResolver, OperationResult result) throws SecurityViolationException, SchemaException { MidPointPrincipal principal = getPrincipal(); boolean allow = isAuthorized(operationUrl, phase, object, delta, target, ownerResolver); if (!allow) { String username = getQuotedUsername(principal); String message; if (target == null && object == null) { message = "User '" + username + "' not authorized for operation " + operationUrl; } else if (target == null) { message = "User '" + username + "' not authorized for operation " + operationUrl + " on " + object; } else { message = "User '" + username + "' not authorized for operation " + operationUrl + " on " + object + " with target " + target; } LOGGER.error("{}", message); AuthorizationException e = new AuthorizationException(message); // +":\n"+((MidPointPrincipal)principal).debugDump()); result.recordFatalError(e.getMessage(), e); throw e; } } private <O extends ObjectType> boolean isApplicable(List<OwnedObjectSpecificationType> objectSpecTypes, PrismObject<O> object, MidPointPrincipal midPointPrincipal, OwnerResolver ownerResolver, String desc, String autzHumanReadableDesc) throws SchemaException { if (objectSpecTypes != null && !objectSpecTypes.isEmpty()) { if (object == null) { LOGGER.trace(" {} not applicable for null {}", autzHumanReadableDesc, desc); return false; } for (OwnedObjectSpecificationType autzObject : objectSpecTypes) { if (isApplicable(autzObject, object, midPointPrincipal, ownerResolver, desc, autzHumanReadableDesc)) { return true; } } return false; } else { LOGGER.trace(" {}: No {} specification in authorization (authorization is applicable)", autzHumanReadableDesc, desc); return true; } } private <O extends ObjectType> boolean isApplicable(ObjectSpecificationType objectSpecType, PrismObject<O> object, MidPointPrincipal principal, OwnerResolver ownerResolver, String desc, String autzHumanReadableDesc) throws SchemaException { if (objectSpecType == null) { LOGGER.trace(" {} not applicable for {} because of null object specification", autzHumanReadableDesc, desc); return false; } SearchFilterType specFilterType = objectSpecType.getFilter(); ObjectReferenceType specOrgRef = objectSpecType.getOrgRef(); QName specTypeQName = objectSpecType.getType(); // now it does not matter if it's unqualified PrismObjectDefinition<O> objectDefinition = object.getDefinition(); // Type if (specTypeQName != null && !QNameUtil.match(specTypeQName, objectDefinition.getTypeName())) { LOGGER.trace(" {} not applicable for {} because of type mismatch, expected {}, was {}", new Object[] { autzHumanReadableDesc, desc, specTypeQName, objectDefinition.getTypeName() }); return false; } // Special List<SpecialObjectSpecificationType> specSpecial = objectSpecType.getSpecial(); if (specSpecial != null && !specSpecial.isEmpty()) { if (specFilterType != null || specOrgRef != null) { throw new SchemaException("Both filter/org and special " + desc + " specification specified in " + autzHumanReadableDesc); } for (SpecialObjectSpecificationType special : specSpecial) { if (special == SpecialObjectSpecificationType.SELF) { String principalOid = principal.getOid(); if (principalOid == null) { // This is a rare case. It should not normally happen. But it may happen in tests // or during initial import. Therefore we are not going to die here. Just ignore it. } else { if (principalOid.equals(object.getOid())) { LOGGER.trace(" {}: 'self' authorization applicable for {}", autzHumanReadableDesc, desc); return true; } else { LOGGER.trace( " {}: 'self' authorization not applicable for {}, principal OID: {}, {} OID {}", new Object[] { autzHumanReadableDesc, desc, principalOid, desc, object.getOid() }); } } } else { throw new SchemaException("Unsupported special " + desc + " specification specified in " + autzHumanReadableDesc + ": " + special); } } return false; } else { LOGGER.trace(" {}: specials empty: {}", autzHumanReadableDesc, specSpecial); } // Filter if (specFilterType != null) { ObjectFilter specFilter = QueryJaxbConvertor.createObjectFilter(object.getCompileTimeClass(), specFilterType, object.getPrismContext()); if (specFilter != null) { ObjectQueryUtil.assertPropertyOnly(specFilter, "Filter in " + autzHumanReadableDesc + " " + desc + " is not property-only filter"); } try { if (!ObjectQuery.match(object, specFilter, matchingRuleRegistry)) { LOGGER.trace(" filter {} not applicable for {}, object OID {}", new Object[] { autzHumanReadableDesc, desc, object.getOid() }); return false; } } catch (SchemaException ex) { throw new SchemaException( "Could not apply " + autzHumanReadableDesc + " for " + object + ". " + ex.getMessage(), ex); } } // Org if (specOrgRef != null) { List<ObjectReferenceType> objParentOrgRefs = object.asObjectable().getParentOrgRef(); List<String> objParentOrgOids = new ArrayList<>(objParentOrgRefs.size()); for (ObjectReferenceType objParentOrgRef : objParentOrgRefs) { objParentOrgOids.add(objParentOrgRef.getOid()); } boolean anySubordinate = repositoryService.isAnySubordinate(specOrgRef.getOid(), objParentOrgOids); if (!anySubordinate) { LOGGER.trace(" org {} not applicable for {}, object OID {} (autz={} parentRefs={})", new Object[] { autzHumanReadableDesc, desc, object.getOid(), specOrgRef.getOid(), objParentOrgOids }); return false; } } if (objectSpecType instanceof OwnedObjectSpecificationType) { // Owner ObjectSpecificationType ownerSpec = ((OwnedObjectSpecificationType) objectSpecType).getOwner(); if (ownerSpec != null) { if (!object.canRepresent(ShadowType.class)) { LOGGER.trace( " {}: owner object spec not applicable for {}, object OID {} because it is not a shadow", new Object[] { autzHumanReadableDesc, desc, object.getOid() }); return false; } if (ownerResolver == null) { ownerResolver = userProfileService; if (ownerResolver == null) { LOGGER.trace( " {}: owner object spec not applicable for {}, object OID {} because there is no owner resolver", new Object[] { autzHumanReadableDesc, desc, object.getOid() }); return false; } } PrismObject<? extends FocusType> owner = ownerResolver .resolveOwner((PrismObject<ShadowType>) object); if (owner == null) { LOGGER.trace( " {}: owner object spec not applicable for {}, object OID {} because it has no owner", new Object[] { autzHumanReadableDesc, desc, object.getOid() }); return false; } boolean ownerApplicable = isApplicable(ownerSpec, owner, principal, ownerResolver, "owner of " + desc, autzHumanReadableDesc); if (!ownerApplicable) { LOGGER.trace( " {}: owner object spec not applicable for {}, object OID {} because owner does not match (owner={})", new Object[] { autzHumanReadableDesc, desc, object.getOid(), owner }); return false; } } } LOGGER.trace(" {} applicable for {} (filter)", autzHumanReadableDesc, desc); return true; } private <O extends ObjectType, T extends ObjectType> boolean isApplicableItem(Authorization autz, PrismObject<O> object, ObjectDelta<O> delta) { List<ItemPathType> itemPaths = autz.getItem(); if (itemPaths == null || itemPaths.isEmpty()) { // No item constraints. Applicable for all items. LOGGER.trace(" items empty"); return true; } for (ItemPathType itemPathType : itemPaths) { ItemPath itemPath = itemPathType.getItemPath(); if (object != null) { Item<?, ?> item = object.findItem(itemPath); if (item != null && !item.isEmpty()) { LOGGER.trace(" applicable object item " + itemPath); return true; } } if (delta != null) { ItemDelta<?, ?> itemDelta = delta.findItemDelta(itemPath); if (itemDelta != null && !itemDelta.isEmpty()) { LOGGER.trace(" applicable delta item " + itemPath); return true; } } } LOGGER.trace(" no applicable item"); return false; } private Collection<ItemPath> getItems(Authorization autz) { List<ItemPathType> itemPaths = autz.getItem(); Collection<ItemPath> items = new ArrayList<>(itemPaths.size()); if (itemPaths != null) { for (ItemPathType itemPathType : itemPaths) { ItemPath itemPath = itemPathType.getItemPath(); items.add(itemPath); } } return items; } /** * Spring security method. It is practically applicable only for simple cases. */ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (object instanceof MethodInvocation) { MethodInvocation methodInvocation = (MethodInvocation) object; // TODO } else if (object instanceof FilterInvocation) { FilterInvocation filterInvocation = (FilterInvocation) object; // TODO } else { SecurityUtil.logSecurityDeny(object, ": Unknown type of secure object"); throw new IllegalArgumentException("Unknown type of secure object"); } Object principalObject = authentication.getPrincipal(); if (!(principalObject instanceof MidPointPrincipal)) { if (authentication.getPrincipal() instanceof String && "anonymousUser".equals(principalObject)) { SecurityUtil.logSecurityDeny(object, ": Not logged in"); throw new InsufficientAuthenticationException("Not logged in."); } throw new IllegalArgumentException("Expected that spring security principal will be of type " + MidPointPrincipal.class.getName() + " but it was " + principalObject.getClass()); } Collection<String> configActions = SecurityUtil.getActions(configAttributes); for (String configAction : configActions) { boolean isAuthorized; try { isAuthorized = isAuthorized(configAction, null, null, null, null, null); } catch (SchemaException e) { throw new SystemException(e.getMessage(), e); } if (isAuthorized) { return; } } SecurityUtil.logSecurityDeny(object, ": Not authorized", null, configActions); // Sparse exception method by purpose. We do not want to expose details to attacker. // Better message is logged. throw new AccessDeniedException("Not authorized"); } @Override public boolean supports(ConfigAttribute attribute) { if (attribute instanceof SecurityConfig) { return true; } else { return false; } } @Override public boolean supports(Class<?> clazz) { if (MethodInvocation.class.isAssignableFrom(clazz)) { return true; } else if (FilterInvocation.class.isAssignableFrom(clazz)) { return true; } else { return false; } } private String getQuotedUsername(Authentication authentication) { String username = "(none)"; Object principal = authentication.getPrincipal(); if (principal != null) { if (principal instanceof MidPointPrincipal) { username = "'" + ((MidPointPrincipal) principal).getUsername() + "'"; } else { username = "(unknown:" + principal + ")"; } } return username; } private String getQuotedUsername(MidPointPrincipal principal) { if (principal == null) { return "(none)"; } return "'" + ((MidPointPrincipal) principal).getUsername() + "'"; } private MidPointPrincipal getMidPointPrincipal() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { LOGGER.warn("No authentication"); return null; } Object principal = authentication.getPrincipal(); if (principal == null) { LOGGER.warn("Null principal"); return null; } if (!(principal instanceof MidPointPrincipal)) { if (authentication.getPrincipal() instanceof String && "anonymousUser".equals(principal)) { LOGGER.trace("AUTZ: deny because user is not logged in"); return null; } LOGGER.warn("Unknown principal type {}", principal.getClass()); return null; } return (MidPointPrincipal) principal; } @Override public <O extends ObjectType> ObjectSecurityConstraints compileSecurityConstraints(PrismObject<O> object, OwnerResolver ownerResolver) throws SchemaException { MidPointPrincipal principal = getMidPointPrincipal(); if (object == null) { throw new IllegalArgumentException("Cannot compile security constraints of null object"); } if (principal == null) { // No need to log, the getMidPointPrincipal() already logs the reason return null; } LOGGER.trace("AUTZ: evaluating security constraints principal={}, object={}", principal, object); ObjectSecurityConstraintsImpl objectSecurityConstraints = new ObjectSecurityConstraintsImpl(); Collection<Authorization> authorities = principal.getAuthorities(); if (authorities != null) { for (GrantedAuthority authority : authorities) { if (authority instanceof Authorization) { Authorization autz = (Authorization) authority; String autzHumanReadableDesc = autz.getHumanReadableDesc(); LOGGER.trace("Evaluating {}", autzHumanReadableDesc); // skip action applicability evaluation. We are interested in all actions // object if (isApplicable(autz.getObject(), object, principal, ownerResolver, "object", autzHumanReadableDesc)) { LOGGER.trace(" {} applicable for object {} (continuing evaluation)", autzHumanReadableDesc, object); } else { LOGGER.trace( " {} not applicable for object {}, none of the object specifications match (breaking evaluation)", autzHumanReadableDesc, object); continue; } // skip target applicability evaluation. We do not have a target here List<String> actions = autz.getAction(); AuthorizationPhaseType phase = autz.getPhase(); AuthorizationDecisionType decision = autz.getDecision(); if (decision == null || decision == AuthorizationDecisionType.ALLOW) { Collection<ItemPath> items = getItems(autz); if (items == null || items.isEmpty()) { applyDecision(objectSecurityConstraints.getActionDecisionMap(), actions, phase, AuthorizationDecisionType.ALLOW); } else { for (ItemPath item : items) { applyItemDecision(objectSecurityConstraints.getItemConstraintMap(), item, actions, phase, AuthorizationDecisionType.ALLOW); } } } else { Collection<ItemPath> items = getItems(autz); if (items == null || items.isEmpty()) { applyDecision(objectSecurityConstraints.getActionDecisionMap(), actions, phase, AuthorizationDecisionType.DENY); } else { for (ItemPath item : items) { applyItemDecision(objectSecurityConstraints.getItemConstraintMap(), item, actions, phase, AuthorizationDecisionType.DENY); } } } } else { LOGGER.warn("Unknown authority type {} in user {}", authority.getClass(), principal.getUsername()); } } } if (LOGGER.isTraceEnabled()) { LOGGER.trace("AUTZ: evaluated security constraints principal={}, object={}:\n{}", principal, object, objectSecurityConstraints.debugDump()); } return objectSecurityConstraints; } private void applyItemDecision(Map<ItemPath, ItemSecurityConstraintsImpl> itemConstraintMap, ItemPath item, List<String> actions, AuthorizationPhaseType phase, AuthorizationDecisionType decision) { ItemSecurityConstraintsImpl entry = itemConstraintMap.get(item); if (entry == null) { entry = new ItemSecurityConstraintsImpl(); itemConstraintMap.put(item, entry); } applyDecision(entry.getActionDecisionMap(), actions, phase, decision); } private void applyDecision(Map<String, PhaseDecisionImpl> actionDecisionMap, List<String> actions, AuthorizationPhaseType phase, AuthorizationDecisionType decision) { for (String action : actions) { if (phase == null) { applyDecisionRequest(actionDecisionMap, action, decision); applyDecisionExecution(actionDecisionMap, action, decision); } else if (phase == AuthorizationPhaseType.REQUEST) { applyDecisionRequest(actionDecisionMap, action, decision); } else if (phase == AuthorizationPhaseType.EXECUTION) { applyDecisionExecution(actionDecisionMap, action, decision); } else { throw new IllegalArgumentException("Unknown phase " + phase); } } } private void applyDecisionRequest(Map<String, PhaseDecisionImpl> actionDecisionMap, String action, AuthorizationDecisionType decision) { PhaseDecisionImpl phaseDecision = actionDecisionMap.get(action); if (phaseDecision == null) { phaseDecision = new PhaseDecisionImpl(); phaseDecision.setRequestDecision(decision); actionDecisionMap.put(action, phaseDecision); } else if (phaseDecision.getRequestDecision() == null || // deny overrides (phaseDecision.getRequestDecision() == AuthorizationDecisionType.ALLOW && decision == AuthorizationDecisionType.DENY)) { phaseDecision.setRequestDecision(decision); } } private void applyDecisionExecution(Map<String, PhaseDecisionImpl> actionDecisionMap, String action, AuthorizationDecisionType decision) { PhaseDecisionImpl phaseDecision = actionDecisionMap.get(action); if (phaseDecision == null) { phaseDecision = new PhaseDecisionImpl(); phaseDecision.setExecDecision(decision); actionDecisionMap.put(action, phaseDecision); } else if (phaseDecision.getExecDecision() == null || // deny overrides (phaseDecision.getExecDecision() == AuthorizationDecisionType.ALLOW && decision == AuthorizationDecisionType.DENY)) { phaseDecision.setExecDecision(decision); } } @Override public <T extends ObjectType, O extends ObjectType> ObjectFilter preProcessObjectFilter(String operationUrl, AuthorizationPhaseType phase, Class<T> objectType, PrismObject<O> object, ObjectFilter origFilter) throws SchemaException { MidPointPrincipal principal = getMidPointPrincipal(); if (principal == null) { throw new IllegalArgumentException("No vaild principal"); } LOGGER.trace("AUTZ: evaluating search pre-process principal={}, objectType={}: orig filter {}", new Object[] { principal, objectType, origFilter }); if (origFilter == null) { origFilter = AllFilter.createAll(); } ObjectFilter finalFilter; if (phase != null) { finalFilter = preProcessObjectFilterInternal(principal, operationUrl, phase, true, objectType, object, origFilter); } else { ObjectFilter filterBoth = preProcessObjectFilterInternal(principal, operationUrl, null, false, objectType, object, origFilter); ObjectFilter filterRequest = preProcessObjectFilterInternal(principal, operationUrl, AuthorizationPhaseType.REQUEST, false, objectType, object, origFilter); ObjectFilter filterExecution = preProcessObjectFilterInternal(principal, operationUrl, AuthorizationPhaseType.EXECUTION, false, objectType, object, origFilter); finalFilter = ObjectQueryUtil.filterOr(filterBoth, ObjectQueryUtil.filterAnd(filterRequest, filterExecution)); } LOGGER.trace("AUTZ: evaluated search pre-process principal={}, objectType={}: {}", new Object[] { principal, objectType, finalFilter }); if (finalFilter instanceof AllFilter) { // compatibility return null; } return finalFilter; } private <T extends ObjectType, O extends ObjectType> ObjectFilter preProcessObjectFilterInternal( MidPointPrincipal principal, String operationUrl, AuthorizationPhaseType phase, boolean includeNullPhase, Class<T> objectType, PrismObject<O> object, ObjectFilter origFilter) throws SchemaException { Collection<Authorization> authorities = principal.getAuthorities(); ObjectFilter securityFilterAllow = null; ObjectFilter securityFilterDeny = null; boolean hasAllowAll = false; if (authorities != null) { for (GrantedAuthority authority : authorities) { if (authority instanceof Authorization) { Authorization autz = (Authorization) authority; LOGGER.trace("Evaluating authorization {}", autz); // action if (!autz.getAction().contains(operationUrl) && !autz.getAction().contains(AuthorizationConstants.AUTZ_ALL_URL)) { LOGGER.trace(" Authorization not applicable for operation {}", operationUrl); continue; } // phase if (autz.getPhase() == phase || (includeNullPhase && autz.getPhase() == null)) { LOGGER.trace(" Authorization is applicable for phases {} (continuing evaluation)", phase); } else { LOGGER.trace(" Authorization is not applicable for phase {}", phase); continue; } // object or target ObjectFilter autzObjSecurityFilter = null; List<OwnedObjectSpecificationType> objectSpecTypes; if (object == null) { // object not present. Therefore we are looking for object here objectSpecTypes = autz.getObject(); } else { // object present. Therefore we are looking for target objectSpecTypes = autz.getTarget(); // .. but we need to decide whether this authorization is applicable to the object if (!isApplicableItem(autz, object, null)) { LOGGER.trace(" Authorization is not applicable for object {}", object); } } boolean applicable = true; if (objectSpecTypes != null && !objectSpecTypes.isEmpty()) { applicable = false; for (OwnedObjectSpecificationType objectSpecType : objectSpecTypes) { ObjectFilter objSpecSecurityFilter = null; TypeFilter objSpecTypeFilter = null; SearchFilterType specFilterType = objectSpecType.getFilter(); ObjectReferenceType specOrgRef = objectSpecType.getOrgRef(); QName specTypeQName = objectSpecType.getType(); PrismObjectDefinition<T> objectDefinition = null; // Type if (specTypeQName != null) { specTypeQName = prismContext.getSchemaRegistry().qualifyTypeName(specTypeQName); PrismObjectDefinition<?> specObjectDef = prismContext.getSchemaRegistry() .findObjectDefinitionByType(specTypeQName); Class<?> specObjectClass = specObjectDef.getCompileTimeClass(); if (!objectType.isAssignableFrom(specObjectClass)) { LOGGER.trace( " Authorization not applicable for object because of type mismatch, authorization {}, query {}", new Object[] { specObjectClass, objectType }); continue; } else { LOGGER.trace( " Authorization is applicable for object because of type match, authorization {}, query {}", new Object[] { specObjectClass, objectType }); // The spec type is a subclass of requested type. So it might be returned from the search. // We need to use type filter. objSpecTypeFilter = TypeFilter.createType(specTypeQName, null); // and now we have a more specific object definition to use later in filter processing objectDefinition = (PrismObjectDefinition<T>) specObjectDef; } } // Owner if (objectSpecType.getOwner() != null) { LOGGER.trace( " Authorization not applicable for object because it has owner specification (this is not applicable for search)"); continue; } applicable = true; // Special List<SpecialObjectSpecificationType> specSpecial = objectSpecType.getSpecial(); if (specSpecial != null && !specSpecial.isEmpty()) { if (specFilterType != null || specOrgRef != null) { throw new SchemaException( "Both filter/org and special object specification specified in authorization"); } ObjectFilter specialFilter = null; for (SpecialObjectSpecificationType special : specSpecial) { if (special == SpecialObjectSpecificationType.SELF) { String principalOid = principal.getOid(); specialFilter = ObjectQueryUtil.filterOr(specialFilter, InOidFilter.createInOid(principalOid)); } else { throw new SchemaException( "Unsupported special object specification specified in authorization: " + special); } } objSpecSecurityFilter = specTypeQName != null ? TypeFilter.createType(specTypeQName, specialFilter) : specialFilter; } else { LOGGER.trace(" specials empty: {}", specSpecial); } // Filter if (specFilterType != null) { if (objectDefinition == null) { objectDefinition = prismContext.getSchemaRegistry() .findObjectDefinitionByCompileTimeClass(objectType); } ObjectFilter specFilter = QueryJaxbConvertor.createObjectFilter(objectDefinition, specFilterType, prismContext); if (specFilter != null) { ObjectQueryUtil.assertNotRaw(specFilter, "Filter in authorization object has undefined items. Maybe a 'type' specification is missing in the authorization?"); ObjectQueryUtil.assertPropertyOnly(specFilter, "Filter in authorization object is not property-only filter"); } LOGGER.trace(" applying property filter " + specFilter); objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter, specFilter); } else { LOGGER.trace(" filter empty"); } // Org if (specOrgRef != null) { OrgFilter orgFilter = OrgFilter.createOrg(specOrgRef.getOid()); objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter, orgFilter); LOGGER.trace(" applying org filter " + orgFilter); } else { LOGGER.trace(" org empty"); } if (objSpecTypeFilter != null) { objSpecTypeFilter.setFilter(objSpecSecurityFilter); objSpecSecurityFilter = objSpecTypeFilter; } autzObjSecurityFilter = ObjectQueryUtil.filterOr(autzObjSecurityFilter, objSpecSecurityFilter); } } else { LOGGER.trace( " No object specification in authorization (authorization is universaly applicable)"); autzObjSecurityFilter = AllFilter.createAll(); } if (applicable) { // authority is applicable to this situation. now we can process the decision. AuthorizationDecisionType decision = autz.getDecision(); if (decision == null || decision == AuthorizationDecisionType.ALLOW) { // allow if (ObjectQueryUtil.isAll(autzObjSecurityFilter)) { // this is "allow all" authorization. hasAllowAll = true; } else { securityFilterAllow = ObjectQueryUtil.filterOr(securityFilterAllow, autzObjSecurityFilter); } } else { // deny if (ObjectQueryUtil.isAll(autzObjSecurityFilter)) { // This is "deny all". We cannot have anything stronger than that. // There is no point in continuing the evaluation. LOGGER.trace("AUTZ search pre-process: principal={}, operation={}: deny all", new Object[] { principal.getUsername(), operationUrl }); return NoneFilter.createNone(); } securityFilterDeny = ObjectQueryUtil.filterOr(securityFilterDeny, autzObjSecurityFilter); } } } else { LOGGER.warn("Unknown authority type {} in user {}", authority.getClass(), principal.getUsername()); } } } ObjectFilter origWithAllowFilter; if (hasAllowAll) { origWithAllowFilter = origFilter; } else if (securityFilterAllow == null) { // Nothing has been allowed. This means default deny. LOGGER.trace("AUTZ search pre-process: principal={}, operation={}: default deny", new Object[] { principal.getUsername(), operationUrl }); return NoneFilter.createNone(); } else { origWithAllowFilter = ObjectQueryUtil.filterAnd(origFilter, securityFilterAllow); } if (securityFilterDeny == null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("AUTZ search pre-process: principal={}, operation={}: allow:\n{}", new Object[] { principal.getUsername(), operationUrl, origWithAllowFilter == null ? "null" : origWithAllowFilter.debugDump() }); } return origWithAllowFilter; } else { ObjectFilter secFilter = ObjectQueryUtil.filterAnd(origWithAllowFilter, NotFilter.createNot(securityFilterDeny)); if (LOGGER.isTraceEnabled()) { LOGGER.trace("AUTZ search pre-process: principal={}, operation={}: allow (with deny clauses):\n{}", new Object[] { principal.getUsername(), operationUrl, secFilter == null ? "null" : secFilter.debugDump() }); } return secFilter; } } }