| /* |
| * Copyright (c) 2013, 2014, 2016, 2019 Eike Stepper (Loehne, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.emf.cdo.security.impl; |
| |
| import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.common.revision.CDORevision; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionProvider; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; |
| import org.eclipse.emf.cdo.eresource.CDOResourceNode; |
| import org.eclipse.emf.cdo.eresource.EresourcePackage; |
| import org.eclipse.emf.cdo.security.PatternStyle; |
| import org.eclipse.emf.cdo.security.ResourceFilter; |
| import org.eclipse.emf.cdo.security.SecurityPackage; |
| import org.eclipse.emf.cdo.security.impl.PermissionImpl.CommitImpactContext; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; |
| |
| import org.eclipse.net4j.util.StringUtil; |
| import org.eclipse.net4j.util.WrappedException; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.ecore.EClass; |
| |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| /** |
| * <!-- begin-user-doc --> |
| * An implementation of the model object '<em><b>Resource Filter</b></em>'. |
| * @since 4.3 |
| * <!-- end-user-doc --> |
| * <p> |
| * The following features are implemented: |
| * </p> |
| * <ul> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#getPath <em>Path</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#getPatternStyle <em>Pattern Style</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isFolders <em>Folders</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isTextResources <em>Text Resources</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isBinaryResources <em>Binary Resources</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isModelResources <em>Model Resources</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isModelObjects <em>Model Objects</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isIncludeParents <em>Include Parents</em>}</li> |
| * <li>{@link org.eclipse.emf.cdo.security.impl.ResourceFilterImpl#isIncludeRoot <em>Include Root</em>}</li> |
| * </ul> |
| * |
| * @generated |
| */ |
| public class ResourceFilterImpl extends PermissionFilterImpl implements ResourceFilter |
| { |
| private static final String USER_TOKEN = "${user}"; |
| |
| private static final int USER_TOKEN_NONE = -1; |
| |
| private static final int USER_TOKEN_UNINITIALIZED = -2; |
| |
| private transient int userTokenPos = USER_TOKEN_UNINITIALIZED; |
| |
| private transient BasicEList<Matcher> matchers; |
| |
| private transient int lastVersion; |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| protected ResourceFilterImpl() |
| { |
| super(); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| protected EClass eStaticClass() |
| { |
| return SecurityPackage.Literals.RESOURCE_FILTER; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public String getPath() |
| { |
| return (String)eGet(SecurityPackage.Literals.RESOURCE_FILTER__PATH, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setPath(String newPath) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__PATH, newPath); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public PatternStyle getPatternStyle() |
| { |
| return (PatternStyle)eGet(SecurityPackage.Literals.RESOURCE_FILTER__PATTERN_STYLE, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setPatternStyle(PatternStyle newPatternStyle) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__PATTERN_STYLE, newPatternStyle); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isFolders() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__FOLDERS, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setFolders(boolean newFolders) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__FOLDERS, newFolders); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isModelResources() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__MODEL_RESOURCES, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setModelResources(boolean newModelResources) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__MODEL_RESOURCES, newModelResources); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isModelObjects() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__MODEL_OBJECTS, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setModelObjects(boolean newModelObjects) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__MODEL_OBJECTS, newModelObjects); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isIncludeParents() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__INCLUDE_PARENTS, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setIncludeParents(boolean newIncludeParents) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__INCLUDE_PARENTS, newIncludeParents); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isIncludeRoot() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__INCLUDE_ROOT, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setIncludeRoot(boolean newIncludeRoot) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__INCLUDE_ROOT, newIncludeRoot); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isTextResources() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__TEXT_RESOURCES, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setTextResources(boolean newTextResources) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__TEXT_RESOURCES, newTextResources); |
| return this; |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated |
| */ |
| @Override |
| public boolean isBinaryResources() |
| { |
| return (Boolean)eGet(SecurityPackage.Literals.RESOURCE_FILTER__BINARY_RESOURCES, true); |
| } |
| |
| /** |
| * <!-- begin-user-doc --> |
| * <!-- end-user-doc --> |
| * @generated NOT |
| */ |
| @Override |
| public ResourceFilter setBinaryResources(boolean newBinaryResources) |
| { |
| eSet(SecurityPackage.Literals.RESOURCE_FILTER__BINARY_RESOURCES, newBinaryResources); |
| return this; |
| } |
| |
| @Override |
| public boolean isImpacted(CommitImpactContext context) |
| { |
| return isResourceTreeImpacted(context); |
| } |
| |
| @Override |
| public String format() |
| { |
| String label = "?"; |
| |
| String path = getPath(); |
| if (path != null) |
| { |
| if (!path.startsWith("/")) |
| { |
| path = "/" + path; |
| } |
| |
| label = path; |
| } |
| |
| String operator = formatOperator(); |
| return "resource" + operator + label; |
| } |
| |
| protected String formatOperator() |
| { |
| PatternStyle patternStyle = getPatternStyle(); |
| switch (patternStyle) |
| { |
| case EXACT: |
| return " == "; |
| |
| case TREE: |
| return " >= "; |
| |
| case ANT: |
| case REGEX: |
| return " ~= "; |
| |
| default: |
| throw new IllegalStateException("Unhandled pattern style: " + patternStyle); |
| } |
| } |
| |
| @Override |
| protected boolean filter(CDORevision revision, CDORevisionProvider revisionProvider, CDOBranchPoint securityContext, int level) throws Exception |
| { |
| if (!preChecks(revision, revisionProvider)) |
| { |
| return false; |
| } |
| |
| String revisionPath = CDORevisionUtil.getResourceNodePath(revision, revisionProvider); |
| |
| BasicEList<Matcher> list = getMatchers(); |
| Object[] matchers = list.data(); |
| |
| int length = list.size(); |
| for (int i = 0; i < length; i++) |
| { |
| Matcher matcher = (Matcher)matchers[i]; |
| if (matcher.matches(revisionPath)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean preChecks(CDORevision revision, CDORevisionProvider revisionProvider) |
| { |
| if (revisionProvider == null) |
| { |
| return false; |
| } |
| |
| EClass eClass = revision.getEClass(); |
| boolean resourceNode = false; |
| |
| // Check folders |
| if (eClass == EresourcePackage.Literals.CDO_RESOURCE_FOLDER) |
| { |
| resourceNode = true; |
| if (!isFolders()) |
| { |
| return false; |
| } |
| } |
| |
| // Check model resources |
| if (eClass == EresourcePackage.Literals.CDO_RESOURCE) |
| { |
| resourceNode = true; |
| |
| boolean rootResource = CDOIDUtil.isNull((CDOID)((InternalCDORevision)revision).getContainerID()); |
| if (rootResource) |
| { |
| if (!isIncludeRoot()) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| if (!isModelResources()) |
| { |
| return false; |
| } |
| } |
| } |
| |
| // Check text resources |
| if (eClass == EresourcePackage.Literals.CDO_TEXT_RESOURCE) |
| { |
| resourceNode = true; |
| if (!isTextResources()) |
| { |
| return false; |
| } |
| } |
| |
| // Check binary resources |
| if (eClass == EresourcePackage.Literals.CDO_BINARY_RESOURCE) |
| { |
| resourceNode = true; |
| if (!isBinaryResources()) |
| { |
| return false; |
| } |
| } |
| |
| // Check model objects |
| boolean modelObject = !resourceNode; |
| if (modelObject && !isModelObjects()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private BasicEList<Matcher> getMatchers() |
| { |
| InternalCDORevision revision = cdoRevision(); |
| if (revision != null) |
| { |
| int currentVersion = revision.getVersion(); |
| if (currentVersion > lastVersion) |
| { |
| userTokenPos = USER_TOKEN_UNINITIALIZED; |
| matchers = null; |
| lastVersion = currentVersion; |
| } |
| |
| if (matchers != null) |
| { |
| return matchers; |
| } |
| } |
| |
| String path = getPath(); |
| PatternStyle patternStyle = getPatternStyle(); |
| boolean includeParents = isIncludeParents(); |
| |
| if (userTokenPos == USER_TOKEN_UNINITIALIZED || revision == null) |
| { |
| userTokenPos = path.indexOf(USER_TOKEN); |
| } |
| |
| if (userTokenPos != USER_TOKEN_NONE) |
| { |
| String user = getUser(); |
| if (user == null || user.length() == 0) |
| { |
| throw new IllegalStateException("User required for evaluation of path " + path); |
| } |
| |
| path = path.substring(0, userTokenPos) + user + path.substring(userTokenPos + USER_TOKEN.length()); |
| } |
| |
| BasicEList<Matcher> list = new BasicEList<>(1); |
| getMatchers(list, path, patternStyle, includeParents); |
| |
| if (userTokenPos == USER_TOKEN_NONE) |
| { |
| // Cache matchers if no user token is specified |
| matchers = list; |
| } |
| |
| return list; |
| } |
| |
| private void getMatchers(BasicEList<Matcher> matchers, String path, PatternStyle patternStyle, boolean includeParents) |
| { |
| Matcher matcher = createMatcher(path, patternStyle); |
| matchers.add(matcher); |
| |
| if (includeParents) |
| { |
| int pos = path.lastIndexOf("/"); |
| if (pos != -1) |
| { |
| path = path.substring(0, pos); |
| patternStyle = matcher.getParentPatternStyle(); |
| |
| getMatchers(matchers, path, patternStyle, includeParents); |
| } |
| } |
| } |
| |
| private Matcher createMatcher(String path, PatternStyle patternStyle) |
| { |
| switch (patternStyle) |
| { |
| case EXACT: |
| return new ExactMatcher(path); |
| |
| case TREE: |
| return new TreeMatcher(path); |
| |
| case ANT: |
| return new AntMatcher(path); |
| |
| case REGEX: |
| return new RegexMatcher(path); |
| |
| default: |
| throw new IllegalStateException("Unhandled pattern style: " + patternStyle); |
| } |
| } |
| |
| public static boolean isResourceTreeImpacted(CommitImpactContext context) |
| { |
| InternalCDORevisionDelta[] revisionDeltas = (InternalCDORevisionDelta[])context.getDirtyObjectDeltas(); |
| |
| for (int i = 0; i < revisionDeltas.length; i++) |
| { |
| InternalCDORevisionDelta revisionDelta = revisionDeltas[i]; |
| |
| // Any tree move might impact this permission |
| CDOFeatureDelta containerDelta = revisionDelta.getFeatureDelta(CDOContainerFeatureDelta.CONTAINER_FEATURE); |
| if (containerDelta != null) |
| { |
| return true; |
| } |
| |
| // Any change of a resource node name might impact this permission |
| CDOFeatureDelta nameDelta = revisionDelta.getFeatureDelta(EresourcePackage.Literals.CDO_RESOURCE_NODE__NAME); |
| if (nameDelta != null) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Matches the path of a {@link CDOResourceNode resource node}. |
| * |
| * @author Eike Stepper |
| */ |
| public interface Matcher |
| { |
| public boolean matches(String revisionPath); |
| |
| public PatternStyle getParentPatternStyle(); |
| } |
| |
| /** |
| * Matches the path of a {@link CDOResourceNode resource node} against a {@link #path} string. |
| * |
| * @author Eike Stepper |
| */ |
| protected static abstract class PathMatcher implements Matcher |
| { |
| protected final String path; |
| |
| public PathMatcher(String path) |
| { |
| this.path = path == null || path.length() == 0 ? "/" : path; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return getClass().getSimpleName() + "[" + path + "]"; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| protected static class ExactMatcher extends PathMatcher |
| { |
| public ExactMatcher(String path) |
| { |
| super(path); |
| } |
| |
| @Override |
| public boolean matches(String revisionPath) |
| { |
| return path.equals(revisionPath); |
| } |
| |
| @Override |
| public PatternStyle getParentPatternStyle() |
| { |
| return PatternStyle.EXACT; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| protected static class TreeMatcher extends PathMatcher |
| { |
| public TreeMatcher(String path) |
| { |
| super(path); |
| } |
| |
| @Override |
| public boolean matches(String revisionPath) |
| { |
| if (revisionPath == null) |
| { |
| return path.length() == 0; |
| } |
| |
| return revisionPath.startsWith(path); |
| } |
| |
| @Override |
| public PatternStyle getParentPatternStyle() |
| { |
| return PatternStyle.EXACT; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| protected static class AntMatcher extends PathMatcher |
| { |
| public AntMatcher(String path) |
| { |
| super(path); |
| } |
| |
| @Override |
| public boolean matches(String revisionPath) |
| { |
| return StringUtil.glob(path, revisionPath); |
| } |
| |
| @Override |
| public PatternStyle getParentPatternStyle() |
| { |
| return PatternStyle.ANT; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| protected static class RegexMatcher implements Matcher |
| { |
| private final Pattern pattern; |
| |
| public RegexMatcher(String path) |
| { |
| try |
| { |
| pattern = Pattern.compile(path); |
| } |
| catch (PatternSyntaxException ex) |
| { |
| throw WrappedException.wrap(ex); |
| } |
| } |
| |
| @Override |
| public boolean matches(String revisionPath) |
| { |
| return pattern.matcher(revisionPath).matches(); |
| } |
| |
| @Override |
| public PatternStyle getParentPatternStyle() |
| { |
| return PatternStyle.REGEX; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return getClass().getSimpleName() + "[" + pattern.pattern() + "]"; |
| } |
| } |
| |
| } // ResourceFilterImpl |