/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.sling.oak.restrictions.impl; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern; import org.apache.jackrabbit.oak.util.TreeUtil; import org.apache.sling.api.SlingConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Implementation of the {@link RestrictionPattern} interface that returns {@code true} if the resource type of the target tree (or the * parent of a target property) is contained in the configured resource type. */ public class ResourceTypePattern implements RestrictionPattern { private static final Logger LOG = LoggerFactory.getLogger(ResourceTypePattern.class); static final String DEFAULT_PATH = "."; static final String PATH_MARKER = "@"; static final String SLING_RESOURCE_TYPE = SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_TYPE; private final String limitedToPath; private final boolean matchChildren; private final Map<String,Set<String>> resourceTypesByPath; ResourceTypePattern(@Nonnull Iterable<String> resourceTypesRaw, String limitedToPath, boolean matchChildren) { this.limitedToPath = limitedToPath; this.matchChildren = matchChildren; Map<String,Set<String>> resourceTypesByPath = new LinkedHashMap<String,Set<String>>(); for (String resourceTypeRaw : resourceTypesRaw) { String path; String resourceType; if(resourceTypeRaw.contains(PATH_MARKER)) { String[] bits = resourceTypeRaw.trim().split(PATH_MARKER, 2); path = bits[1]; resourceType = bits[0]; } else { path = DEFAULT_PATH; resourceType = resourceTypeRaw; } Set<String> resourceTypesForPath = resourceTypesByPath.get(path); if(resourceTypesForPath==null) { resourceTypesForPath = new HashSet<String>(); resourceTypesByPath.put(path, resourceTypesForPath); } resourceTypesForPath.add(resourceType); } this.resourceTypesByPath = Collections.unmodifiableMap(resourceTypesByPath); LOG.trace("pattern setup with resourceTypesByPath={}", this.resourceTypesByPath); } String getLimitedToPath() { return limitedToPath; } boolean isMatchChildren() { return matchChildren; } @Override public boolean matches(@Nonnull Tree tree, @Nullable PropertyState property) { boolean isMatch = matchesAtTree(tree); if(!isMatch && matchChildren) { // try parent hierarchy Tree treeCursor = tree; while(!isMatch && !treeCursor.isRoot()) { treeCursor = treeCursor.getParent(); if(!treeCursor.getPath().startsWith(limitedToPath)) { if(LOG.isTraceEnabled()) { LOG.trace("Breaking parent traversal loop: tree={}, limitedToPath={}", treeCursor.getPath(), limitedToPath); } break; } isMatch = matchesAtTree(treeCursor); } } if(LOG.isDebugEnabled()) { LOG.debug("Match for "+tree.getPath()+": "+ (isMatch ? "YES":"NO") + " ("+this+")"); } return isMatch; } private boolean matchesAtTree(Tree tree) { boolean isResourceTypeMatch = false; for (String path : resourceTypesByPath.keySet()) { Tree treeToCheck = tree; // the default if e.g. just the resource type without @path is given if(!DEFAULT_PATH.equals(path)) { try { String[] segments = path.split("/"); for (String string : segments) { treeToCheck = treeToCheck.getChild(string); } } catch (IllegalArgumentException e) { continue; // continue and ignore if path is not found } } Set<String> resourceTypesForPath = resourceTypesByPath.get(path); String actualResourceType = TreeUtil.getString(treeToCheck, SLING_RESOURCE_TYPE); isResourceTypeMatch = resourceTypesForPath.contains(actualResourceType); if(LOG.isTraceEnabled()) { LOG.trace("isResourceTypeMatch={} (checked at path {} at sub path {})", new Object[]{isResourceTypeMatch, tree.getPath(), path}); } if(isResourceTypeMatch) { break; // return as quickly as possible } } return isResourceTypeMatch; } @Override public boolean matches(@Nonnull String path) { return false; } @Override public boolean matches() { // node type pattern never matches for repository level permissions return false; } // -------------------------------------------------------------< Object >--- @Override public String toString() { return "ResourceTypePattern [limitedToPath=" + limitedToPath + ", matchChildren=" + matchChildren + ", resourceTypesByPath=" + resourceTypesByPath + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((limitedToPath == null) ? 0 : limitedToPath.hashCode()); result = prime * result + (matchChildren ? 1231 : 1237); result = prime * result + ((resourceTypesByPath == null) ? 0 : resourceTypesByPath.hashCode()); return result; } @Override public boolean equals(Object obj) { if(this == obj) return true; if(obj == null) return false; if(getClass() != obj.getClass()) return false; ResourceTypePattern other = (ResourceTypePattern) obj; if(limitedToPath == null) { if(other.limitedToPath != null) return false; } else if(!limitedToPath.equals(other.limitedToPath)) return false; if(matchChildren != other.matchChildren) return false; if(resourceTypesByPath == null) { if(other.resourceTypesByPath != null) return false; } else if(!resourceTypesByPath.equals(other.resourceTypesByPath)) return false; return true; } }