You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:21:44 UTC

[sling-org-apache-sling-resourceaccesssecurity] 01/24: SLING-3435 - ResourceAccessSecurity does not secure access for update operations

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceaccesssecurity.git

commit 535810d6ffcb8180308f0c6ea1b2ab3c8ee05f60
Author: Mike Müller <my...@apache.org>
AuthorDate: Sun Mar 16 19:17:41 2014 +0000

    SLING-3435 - ResourceAccessSecurity does not secure access for update operations
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1578141 13f79535-47bb-0310-9956-ffa450edef68
---
 README.txt                                         |  27 ++
 pom.xml                                            | 141 +++++++++
 .../AllowingResourceAccessGate.java                | 129 ++++++++
 .../resourceaccesssecurity/ResourceAccessGate.java | 215 +++++++++++++
 .../impl/AccessGateResourceWrapper.java            |  87 ++++++
 .../ApplicationResourceAccessSecurityImpl.java     |  42 +++
 .../impl/ProviderResourceAccessSecurityImpl.java   |  42 +++
 .../impl/ReadOnlyValueMapWrapper.java              |  63 ++++
 .../impl/ResourceAccessGateHandler.java            | 122 ++++++++
 .../impl/ResourceAccessSecurityImpl.java           | 347 +++++++++++++++++++++
 .../sling/resourceaccesssecurity/package-info.java |  24 ++
 .../impl/ResourceAccessSecurityImplTests.java      | 189 +++++++++++
 12 files changed, 1428 insertions(+)

diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..948b6c2
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,27 @@
+Apache Sling Resource Access Security
+
+This bundle provides in implementation of the ResourceAccessSecurity
+
+Getting Started
+===============
+
+This component uses a Maven 3 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+3.0.3 or later. We recommend to use the latest Maven version.
+
+If you have Maven 3 installed, you can compile and
+package the jar using the following command:
+
+    mvn package
+
+See the Maven 3 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.apache.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+    svn checkout http://svn.apache.org/repos/asf/sling/trunk/resourceresolver/core
+
+See the Subversion documentation for other source control features.
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2bafb55
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>18</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.resourceaccesssecurity</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Resource Access Security</name>
+    <description>
+        This bundle provides in implementation of the ResourceAccessSecurity service
+    </description>
+
+    <scm>
+        <connection>
+            scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/resourceaccesssecurity/core
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/resourceaccesssecurity/core
+        </developerConnection>
+        <url>
+            http://svn.apache.org/viewvc/sling/trunk/bundles/resourceaccesssecurity/core
+        </url>
+    </scm>
+
+    <properties>
+        <site.javadoc.exclude>**.internal.**</site.javadoc.exclude>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>maven-sling-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-adapter-metadata</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>generate-adapter-metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <dependencies>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.5.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+           <groupId>commons-collections</groupId>
+           <artifactId>commons-collections</artifactId>
+           <version>3.2.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>adapter-annotations</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+     </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/AllowingResourceAccessGate.java b/src/main/java/org/apache/sling/resourceaccesssecurity/AllowingResourceAccessGate.java
new file mode 100644
index 0000000..1e7d8c7
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/AllowingResourceAccessGate.java
@@ -0,0 +1,129 @@
+/*
+ * 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.resourceaccesssecurity;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.security.AccessSecurityException;
+
+/**
+ * This abstract implementation of the <code>ResourceAccessGate</code> can be
+ * used to implement own resource access gates.
+ * This implementation simply allows operations, restricting implementations
+ * just need to overwrite the operations they want to restrict.
+ */
+public abstract class AllowingResourceAccessGate implements ResourceAccessGate {
+
+    @Override
+    public GateResult canRead(final Resource resource) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canCreate(final String absPathName,
+            final ResourceResolver resourceResolver) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canUpdate(final Resource resource) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canDelete(final Resource resource) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canExecute(final Resource resource) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canReadValue(final Resource resource, final String valueName) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canCreateValue(final Resource resource, final String valueName) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canUpdateValue(final Resource resource, final String valueName) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public GateResult canDeleteValue(final Resource resource, final String valueName) {
+        return GateResult.DONTCARE;
+    }
+
+    @Override
+    public String transformQuery(final String query, final String language,
+            final ResourceResolver resourceResolver) throws AccessSecurityException {
+        return query;
+    }
+
+    @Override
+    public boolean hasReadRestrictions(final ResourceResolver resourceResolver) {
+        return false;
+    }
+
+    @Override
+    public boolean hasCreateRestrictions(final ResourceResolver resourceResolver) {
+        return false;
+    }
+
+    @Override
+    public boolean hasUpdateRestrictions(final ResourceResolver resourceResolver) {
+        return false;
+    }
+
+    @Override
+    public boolean hasDeleteRestrictions(final ResourceResolver resourceResolver) {
+        return false;
+    }
+
+    @Override
+    public boolean hasExecuteRestrictions(final ResourceResolver resourceResolver) {
+        return false;
+    }
+
+    @Override
+    public boolean canReadAllValues(final Resource resource) {
+        return true;
+    }
+
+    @Override
+    public boolean canCreateAllValues(final Resource resource) {
+        return true;
+    }
+
+    @Override
+    public boolean canUpdateAllValues(final Resource resource) {
+        return true;
+    }
+
+    @Override
+    public boolean canDeleteAllValues(final Resource resource) {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/ResourceAccessGate.java b/src/main/java/org/apache/sling/resourceaccesssecurity/ResourceAccessGate.java
new file mode 100644
index 0000000..4b096e8
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/ResourceAccessGate.java
@@ -0,0 +1,215 @@
+/*
+ * 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.resourceaccesssecurity;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.security.AccessSecurityException;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * The <code>ResourceAccessGate</code> defines a service API which might be used
+ * to make some restrictions to accessing resources.
+ *
+ * Implementations of this service interface must be registered like
+ * ResourceProvider with a path (like provider.roots). If different
+ * ResourceAccessGateService services match a path, not only the
+ * ResourceAccessGateService with the longest path will be called, but all of
+ * them, that's in contrast to the ResourceProvider, but in this case more
+ * logical (and secure!). The gates will be called in the order of the
+ * service ranking.
+ * If one of the gates grants access for a given operation access will be granted.
+ *
+ * service properties:
+ * <ul>
+ * <li><b>path</b>: regexp to define on which paths the service should be called
+ * (default .*)</li>
+ * <li><b>operations</b>: set of operations on which the service should be
+ * called ("read,create,update,delete,execute", default all of them)</li>
+ * <li><b>finaloperations</b>: set of operations on which the service answer is
+ * final and no further service should be called (default none of them), except
+ * the GateResult is {@link GateResult.DONTCARE}</li>
+ * </ul>
+ *
+ * The resource access gate can either have the context {@link #PROVIDER_CONTEXT},
+ * in this case the gate is only applied to resource providers requesting the
+ * security checks. Or the context can be {@link #APPLICATION_CONTEXT}. In this
+ * case the access gate is invoked for the whole resource tree.
+ * This is indicated by the required service property {@link #CONTEXT}. If the
+ * property is missing or invalid, the service is ignored.
+ */
+@ConsumerType
+public interface ResourceAccessGate {
+
+    /**
+     * The service name to use when registering implementations of this
+     * interface as services (value is
+     * "org.apache.sling.api.resource.ResourceAccessGate").
+     */
+    String SERVICE_NAME = ResourceAccessGate.class.getName();
+
+    /**
+     * The name of the service registration property containing the context
+     * of this service. Allowed values are {@link #APPLICATION_CONTEXT} and
+     * {@link #PROVIDER_CONTEXT}.
+     * This property is required and has no default value.
+     * (value is "access.context")
+     */
+    String CONTEXT = "access.context";
+
+    /**
+     * Allowed value for the {@link #CONTEXT} service registration property.
+     * Services marked with this context are applied to all resources.
+     */
+    String APPLICATION_CONTEXT = "application";
+
+    /**
+     * Allowed value for the {@link #CONTEXT} service registration property.
+     * Services marked with this context are only applied to resource
+     * providers which indicate the additional checks with the
+     * {@link org.apache.sling.api.resource.ResourceProvider#USE_RESOURCE_ACCESS_SECURITY}
+     * property.
+     */
+    String PROVIDER_CONTEXT = "provider";
+
+    /**
+     * The name of the service registration property containing the path as a
+     * regular expression for which the service should be called (value is
+     * "path").
+     */
+    String PATH = "path";
+
+    /**
+     * The name of the service registration property containing the operations
+     * for which the service should be called, defaults to all the operations
+     * (value is "operations").
+     */
+    String OPERATIONS = "operations";
+
+    /**
+     * The name of the service registration property containing the operations
+     * for which the service should be called and no further service should be
+     * called after this, except the services returns DONTCARE as result,
+     * default is empty (non of them are final) (value is "finaloperations").
+     */
+    String FINALOPERATIONS = "finaloperations";
+
+    /**
+     * <code>GateResult</code> defines 3 possible states which can be returned
+     * by the different canXXX methods of this interface.
+     * <ul>
+     * <li>GRANTED: means no restrictions</li>
+     * <li>DENIED: means no permission for the requested action</li>
+     * <li>DONTCARE: means that the implementation of the service has no
+     * information or can't decide and therefore neither can't grant or deny
+     * access</li>
+     * </ul>
+     */
+    public enum GateResult {
+        GRANTED, DENIED, DONTCARE
+    };
+
+    public enum Operation {
+        READ("read"), CREATE("create"), UPDATE("update"), DELETE("delete"), EXECUTE(
+                "execute");
+
+        private String text;
+
+        Operation(String text) {
+            this.text = text;
+        }
+
+        public static Operation fromString(String opAsString) {
+            Operation returnValue = null;
+
+            for (Operation op : Operation.values()) {
+                if (opAsString.equals(op.getText())) {
+                    returnValue = op;
+                    break;
+                }
+            }
+
+            return returnValue;
+        }
+
+        public String getText() {
+            return this.text;
+        }
+    }
+
+    public GateResult canRead(Resource resource);
+
+    public GateResult canCreate(String absPathName,
+            ResourceResolver resourceResolver);
+
+    public GateResult canUpdate(Resource resource);
+
+    public GateResult canDelete(Resource resource);
+
+    public GateResult canExecute(Resource resource);
+
+    public GateResult canReadValue(Resource resource, String valueName);
+
+    public GateResult canCreateValue(Resource resource, String valueName);
+
+    public GateResult canUpdateValue(Resource resource, String valueName);
+
+    public GateResult canDeleteValue(Resource resource, String valueName);
+
+    /**
+     * Allows to transform the query based on the current user's credentials.
+     * Can be used to narrow down queries to omit results that the current user
+     * is not allowed to see anyway, speeding up downstream access control.
+     *
+     * Query transformations are not critical with respect to access control as
+     * results are checked using the canRead.. methods anyway.
+     *
+     * @param query
+     *            the query
+     * @param language
+     *            the language in which the query is expressed
+     * @param resourceResolver
+     *            the resource resolver which resolves the query
+     * @return the transformed query
+     * @throws AccessSecurityException
+     */
+    public String transformQuery(String query, String language,
+            ResourceResolver resourceResolver) throws AccessSecurityException;
+
+    /* for convenience (and performance) */
+    public boolean hasReadRestrictions(ResourceResolver resourceResolver);
+
+    public boolean hasCreateRestrictions(ResourceResolver resourceResolver);
+
+    public boolean hasUpdateRestrictions(ResourceResolver resourceResolver);
+
+    public boolean hasDeleteRestrictions(ResourceResolver resourceResolver);
+
+    public boolean hasExecuteRestrictions(ResourceResolver resourceResolver);
+
+    public boolean canReadAllValues(Resource resource);
+
+    public boolean canCreateAllValues(Resource resource);
+
+    public boolean canUpdateAllValues(Resource resource);
+
+    public boolean canDeleteAllValues(Resource resource);
+
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/AccessGateResourceWrapper.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/AccessGateResourceWrapper.java
new file mode 100644
index 0000000..fd93b2f
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/AccessGateResourceWrapper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceWrapper;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+
+/**
+ * The <code>AccessGateResourceWrapper</code> wraps a <code>Resource</code> and
+ * intercepts calls to adaptTo to wrap the adapted <code>ValueMap</code> or
+ * also a <code>ModifiableValueMap</code> to enforce access rules defined
+ * by implementations of <code>ResourceAccessGate</code>
+ *
+ */
+public class AccessGateResourceWrapper extends ResourceWrapper {
+
+    private final List<ResourceAccessGate> accessGatesForReadValues;
+    private final boolean modifiable;
+
+    /**
+     * Creates a new wrapper instance delegating all method calls to the given
+     * <code>resource</code>, but intercepts the calls with checks to the
+     * applied ResourceAccessGate instances for read and/or update values.
+     * 
+     * @param resource resource to protect
+     * @param accessGatesForReadForValues list of access gates to ask when reading values. If 
+     *      the list is <code>null</code> or empty there are no read restrictions
+     * @param modifiable if <code>true</code> the resource can be updated
+     */
+    public AccessGateResourceWrapper(final Resource resource,
+                                     final List<ResourceAccessGate> accessGatesForReadForValues,
+                                     final boolean modifiable ) {
+        super( resource );
+        this.accessGatesForReadValues = accessGatesForReadForValues;
+        this.modifiable = modifiable;
+    }
+
+    /**
+     * Returns the value of calling <code>adaptTo</code> on the
+     * {@link #getResource() wrapped resource}.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        // we do not support the deprecated PersistableValueMap
+        AdapterType adapter = getResource().adaptTo(type);
+
+        if (adapter != null && !modifiable) {
+            if (type == ModifiableValueMap.class) {
+                adapter = null;
+            }
+            else if (type == Map.class || type == ValueMap.class) {
+                // protect also against accidental modifications when changes are done in an adapted map
+                adapter = (AdapterType) new ReadOnlyValueMapWrapper((Map) adapter);
+            }
+        }
+
+
+        return adapter;
+
+
+    }
+
+
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ApplicationResourceAccessSecurityImpl.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ApplicationResourceAccessSecurityImpl.java
new file mode 100644
index 0000000..e784236
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ApplicationResourceAccessSecurityImpl.java
@@ -0,0 +1,42 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.security.ResourceAccessSecurity;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+
+@Component
+@Service(value=ResourceAccessSecurity.class)
+@Property(name=ResourceAccessSecurity.CONTEXT, value=ResourceAccessSecurity.APPLICATION_CONTEXT)
+@Reference(name="ResourceAccessGate", referenceInterface=ResourceAccessGate.class,
+           cardinality=ReferenceCardinality.MANDATORY_MULTIPLE,
+           policy=ReferencePolicy.DYNAMIC,
+           target="(" + ResourceAccessGate.CONTEXT + "=" + ResourceAccessGate.APPLICATION_CONTEXT + ")")
+public class ApplicationResourceAccessSecurityImpl extends ResourceAccessSecurityImpl {
+
+    public ApplicationResourceAccessSecurityImpl() {
+        super(true);
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ProviderResourceAccessSecurityImpl.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ProviderResourceAccessSecurityImpl.java
new file mode 100644
index 0000000..80fd43e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ProviderResourceAccessSecurityImpl.java
@@ -0,0 +1,42 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.security.ResourceAccessSecurity;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+
+@Component
+@Service(value=ResourceAccessSecurity.class)
+@Property(name=ResourceAccessSecurity.CONTEXT, value=ResourceAccessSecurity.PROVIDER_CONTEXT)
+@Reference(name="ResourceAccessGate", referenceInterface=ResourceAccessGate.class,
+           cardinality=ReferenceCardinality.MANDATORY_MULTIPLE,
+           policy=ReferencePolicy.DYNAMIC,
+           target="(" + ResourceAccessGate.CONTEXT + "=" + ResourceAccessGate.PROVIDER_CONTEXT + ")")
+public class ProviderResourceAccessSecurityImpl extends ResourceAccessSecurityImpl {
+
+    public ProviderResourceAccessSecurityImpl() {
+        super(false);
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ReadOnlyValueMapWrapper.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ReadOnlyValueMapWrapper.java
new file mode 100644
index 0000000..43ac8db
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ReadOnlyValueMapWrapper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+/**
+ *  Wrapper class that does protect the underlying map from modifications.
+ */
+public class ReadOnlyValueMapWrapper extends ValueMapDecorator
+        implements ValueMap {
+
+    /**
+     * Creates a new wrapper around a given map.
+     *
+     * @param base wrapped object
+     */
+    public ReadOnlyValueMapWrapper(Map<String, Object> base) {
+        super(base);
+    }
+
+    @Override
+    public Object put(String key, Object value) {
+        // TODO we probably should log this as a warning
+        return null;
+    }
+
+    @Override
+    public Object remove(Object key) {
+        // TODO we probably should log this as a warning
+        return null;
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ?> t) {
+        // TODO we probably should log this as a warning
+    }
+
+    @Override
+    public void clear() {
+        // TODO we probably should log this as a warning
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessGateHandler.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessGateHandler.java
new file mode 100644
index 0000000..7cb94d6
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessGateHandler.java
@@ -0,0 +1,122 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+import org.osgi.framework.ServiceReference;
+
+public class ResourceAccessGateHandler implements Comparable<ResourceAccessGateHandler> {
+
+    private final ResourceAccessGate resourceAccessGate;
+
+    private final ServiceReference reference;
+
+    private final Pattern pathPattern;
+    private final Set<ResourceAccessGate.Operation> operations = new HashSet<ResourceAccessGate.Operation>();
+    private final Set<ResourceAccessGate.Operation> finalOperations = new HashSet<ResourceAccessGate.Operation>();
+
+    /**
+     * constructor
+     */
+    public ResourceAccessGateHandler ( final ServiceReference resourceAccessGateRef ) {
+        this.reference = resourceAccessGateRef;
+
+        resourceAccessGate = (ResourceAccessGate) resourceAccessGateRef.getBundle().
+                getBundleContext().getService(resourceAccessGateRef);
+        // extract the service property "path"
+        final String path = (String) resourceAccessGateRef.getProperty(ResourceAccessGate.PATH);
+        if ( path != null ) {
+            pathPattern = Pattern.compile(path);
+        } else {
+            pathPattern = Pattern.compile(".*");
+        }
+
+        // extract the service property "operations"
+        final String ops = PropertiesUtil.toString( resourceAccessGateRef.getProperty(ResourceAccessGate.OPERATIONS), null );
+        if ( ops != null && ops.length() > 0 ) {
+            final String[] opsArray = ops.split( "," );
+            for (final String opAsString : opsArray) {
+                final ResourceAccessGate.Operation operation = ResourceAccessGate.Operation.fromString(opAsString);
+                if ( operation != null ) {
+                    operations.add(operation);
+                }
+            }
+        } else {
+           for (final ResourceAccessGate.Operation op : ResourceAccessGate.Operation.values() ) {
+               operations.add(op);
+           }
+        }
+
+        // extract the service property "finaloperations"
+        final String finalOps = PropertiesUtil.toString(resourceAccessGateRef.getProperty(ResourceAccessGate.FINALOPERATIONS), null );
+        if ( finalOps != null && finalOps.length() > 0 ) {
+            final String[] finOpsArray = finalOps.split( "," );
+            for (final String opAsString : finOpsArray) {
+                final ResourceAccessGate.Operation operation = ResourceAccessGate.Operation.fromString(opAsString);
+                if ( operation != null ) {
+                    finalOperations.add(operation);
+                }
+            }
+        }
+
+    }
+
+    public boolean matches ( final String path, final ResourceAccessGate.Operation operation ) {
+        boolean returnValue = false;
+
+        if ( operations.contains( operation ) ) {
+            final Matcher match = pathPattern.matcher(path);
+            returnValue = match.matches();
+        }
+
+        return returnValue;
+    }
+
+    public boolean isFinalOperation( final ResourceAccessGate.Operation operation ) {
+        return finalOperations.contains(operation);
+    }
+
+    public ResourceAccessGate getResourceAccessGate () {
+        return resourceAccessGate;
+    }
+
+    @Override
+    public int compareTo(final ResourceAccessGateHandler o) {
+        return this.reference.compareTo(o.reference);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if ( obj instanceof ResourceAccessGateHandler ) {
+            return ((ResourceAccessGateHandler)obj).reference.equals(this.reference);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.reference.hashCode();
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImpl.java b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImpl.java
new file mode 100644
index 0000000..72279e5
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImpl.java
@@ -0,0 +1,347 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.security.AccessSecurityException;
+import org.apache.sling.api.security.ResourceAccessSecurity;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate.GateResult;
+import org.osgi.framework.ServiceReference;
+
+public abstract class ResourceAccessSecurityImpl implements ResourceAccessSecurity {
+
+    private List<ResourceAccessGateHandler> allHandlers = Collections.emptyList();
+
+    private final boolean defaultAllow;
+
+    public ResourceAccessSecurityImpl(final boolean defaultAllow) {
+        this.defaultAllow = defaultAllow;
+    }
+
+    /**
+     * This method returns either an iterator delivering the matching handlers
+     * or <code>null</code>.
+     */
+    private Iterator<ResourceAccessGateHandler> getMatchingResourceAccessGateHandlerIterator(
+            final String path, final ResourceAccessGate.Operation operation) {
+        //
+        // TODO: maybe caching some frequent paths with read operation would be
+        // a good idea
+        //
+        final List<ResourceAccessGateHandler> handlers = allHandlers;
+        if (handlers.size() > 0) {
+
+            final Iterator<ResourceAccessGateHandler> iter = handlers.iterator();
+            return new Iterator<ResourceAccessGateHandler>() {
+
+                private ResourceAccessGateHandler next;
+
+                {
+                    peek();
+                }
+
+                private void peek() {
+                    this.next = null;
+                    while ( iter.hasNext() && next == null ) {
+                        final ResourceAccessGateHandler handler = iter.next();
+                        if (handler.matches(path, operation)) {
+                            next = handler;
+                        }
+                    }
+                }
+
+                @Override
+                public boolean hasNext() {
+                    return next != null;
+                }
+
+                @Override
+                public ResourceAccessGateHandler next() {
+                    if ( next == null ) {
+                        throw new NoSuchElementException();
+                    }
+                    final ResourceAccessGateHandler handler = this.next;
+                    peek();
+                    return handler;
+                }
+
+                @Override
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+
+        return null;
+    }
+
+    @Override
+    public Resource getReadableResource(final Resource resource) {
+        Resource returnValue = (this.defaultAllow ? resource : null);
+
+        final Iterator<ResourceAccessGateHandler> accessGateHandlers = getMatchingResourceAccessGateHandlerIterator(
+                resource.getPath(), ResourceAccessGate.Operation.READ);
+
+        GateResult finalGateResult = null;
+        List<ResourceAccessGate> accessGatesForReadValues = null;
+        boolean canReadAllValues = false;
+
+
+        if ( accessGateHandlers != null ) {
+
+            while ( accessGateHandlers.hasNext() ) {
+                final ResourceAccessGateHandler resourceAccessGateHandler  = accessGateHandlers.next();
+
+                final GateResult gateResult = resourceAccessGateHandler.getResourceAccessGate().canRead(resource);
+                if (!canReadAllValues && gateResult == GateResult.GRANTED) {
+                    if (resourceAccessGateHandler.getResourceAccessGate().canReadAllValues(resource)) {
+                        canReadAllValues = true;
+                        accessGatesForReadValues = null;
+                    } else {
+                        if (accessGatesForReadValues == null) {
+                            accessGatesForReadValues = new ArrayList<ResourceAccessGate>();
+                        }
+                        accessGatesForReadValues.add(resourceAccessGateHandler.getResourceAccessGate());
+                    }
+                }
+                if (finalGateResult == null) {
+                    finalGateResult = gateResult;
+                } else if (finalGateResult != GateResult.GRANTED && gateResult != GateResult.DONTCARE) {
+                    finalGateResult = gateResult;
+                }
+                // stop checking if the operation is final and the result not GateResult.DONTCARE
+                if (gateResult != GateResult.DONTCARE  && resourceAccessGateHandler.isFinalOperation(ResourceAccessGate.Operation.READ)) {
+                    break;
+                }
+            }
+
+
+            // return null if access is denied or no ResourceAccessGate is present
+            if (finalGateResult == null || finalGateResult == GateResult.DENIED) {
+                returnValue = null;
+            } else if (finalGateResult == GateResult.DONTCARE) {
+                returnValue = (this.defaultAllow ? resource : null);
+            } else if (finalGateResult == GateResult.GRANTED ) {
+                returnValue = resource;
+            }
+        }
+
+        boolean canUpdateResource = canUpdate(resource);
+
+        // wrap Resource if read access is not or partly (values) not granted
+        if (returnValue != null) {
+            if( !canReadAllValues || !canUpdateResource ) {
+                returnValue = new AccessGateResourceWrapper(returnValue,
+                        accessGatesForReadValues,
+                        canUpdateResource);
+            }
+        }
+
+        return returnValue;
+    }
+
+    @Override
+    public boolean canCreate(final String path,
+            final ResourceResolver resolver) {
+        final Iterator<ResourceAccessGateHandler> handlers = getMatchingResourceAccessGateHandlerIterator(
+                path, ResourceAccessGate.Operation.CREATE);
+        boolean result = this.defaultAllow;
+        if ( handlers != null ) {
+            GateResult finalGateResult = null;
+
+            while ( handlers.hasNext() ) {
+                final ResourceAccessGateHandler resourceAccessGateHandler  = handlers.next();
+
+                final GateResult gateResult = resourceAccessGateHandler.getResourceAccessGate().canCreate(path, resolver);
+                if (finalGateResult == null) {
+                    finalGateResult = gateResult;
+                } else if (finalGateResult != GateResult.GRANTED && gateResult != GateResult.DONTCARE) {
+                    finalGateResult = gateResult;
+                }
+                if (finalGateResult == GateResult.GRANTED || gateResult != GateResult.DONTCARE && 
+                        resourceAccessGateHandler.isFinalOperation(ResourceAccessGate.Operation.CREATE)) {
+                    break;
+                }
+            }
+
+            if ( finalGateResult == GateResult.GRANTED ) {
+                result = true;
+            } else if ( finalGateResult == GateResult.DENIED ) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean canUpdate(final Resource resource) {
+        final Iterator<ResourceAccessGateHandler> handlers = getMatchingResourceAccessGateHandlerIterator(
+                resource.getPath(), ResourceAccessGate.Operation.UPDATE);
+        boolean result = this.defaultAllow;
+        if ( handlers != null ) {
+            GateResult finalGateResult = null;
+
+            while ( handlers.hasNext() ) {
+                final ResourceAccessGateHandler resourceAccessGateHandler  = handlers.next();
+
+                final GateResult gateResult = resourceAccessGateHandler.getResourceAccessGate().canUpdate(resource);
+                if (finalGateResult == null) {
+                    finalGateResult = gateResult;
+                } else if (finalGateResult != GateResult.GRANTED && gateResult != GateResult.DONTCARE) {
+                    finalGateResult = gateResult;
+                }
+                if (finalGateResult == GateResult.GRANTED || gateResult != GateResult.DONTCARE && 
+                        resourceAccessGateHandler.isFinalOperation(ResourceAccessGate.Operation.UPDATE)) {
+                    break;
+                }
+            }
+
+            if ( finalGateResult == GateResult.GRANTED ) {
+                result = true;
+            } else if ( finalGateResult == GateResult.DENIED ) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean canDelete(final Resource resource) {
+        final Iterator<ResourceAccessGateHandler> handlers = getMatchingResourceAccessGateHandlerIterator(
+                resource.getPath(), ResourceAccessGate.Operation.DELETE);
+        boolean result = this.defaultAllow;
+        if ( handlers != null ) {
+            GateResult finalGateResult = null;
+
+            while ( handlers.hasNext() ) {
+                final ResourceAccessGateHandler resourceAccessGateHandler  = handlers.next();
+
+                final GateResult gateResult = resourceAccessGateHandler.getResourceAccessGate().canDelete(resource);
+                if (finalGateResult == null) {
+                    finalGateResult = gateResult;
+                } else if (finalGateResult != GateResult.GRANTED && gateResult != GateResult.DONTCARE) {
+                    finalGateResult = gateResult;
+                }
+                if (finalGateResult == GateResult.GRANTED || gateResult != GateResult.DONTCARE && 
+                        resourceAccessGateHandler.isFinalOperation(ResourceAccessGate.Operation.DELETE)) {
+                    break;
+                }
+            }
+
+            if ( finalGateResult == GateResult.GRANTED ) {
+                result = true;
+            } else if ( finalGateResult == GateResult.DENIED ) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean canExecute(final Resource resource) {
+        final Iterator<ResourceAccessGateHandler> handlers = getMatchingResourceAccessGateHandlerIterator(
+                resource.getPath(), ResourceAccessGate.Operation.EXECUTE);
+        boolean result = this.defaultAllow;
+        if ( handlers != null ) {
+            GateResult finalGateResult = null;
+
+            while ( handlers.hasNext() ) {
+                final ResourceAccessGateHandler resourceAccessGateHandler  = handlers.next();
+
+                final GateResult gateResult = resourceAccessGateHandler.getResourceAccessGate().canExecute(resource);
+                if (finalGateResult == null) {
+                    finalGateResult = gateResult;
+                } else if (finalGateResult != GateResult.GRANTED && gateResult != GateResult.DONTCARE) {
+                    finalGateResult = gateResult;
+                }
+                if (finalGateResult == GateResult.GRANTED || gateResult != GateResult.DONTCARE && resourceAccessGateHandler.isFinalOperation(ResourceAccessGate.Operation.EXECUTE)) {
+                    break;
+                }
+            }
+
+            if ( finalGateResult == GateResult.GRANTED ) {
+                result = true;
+            } else if ( finalGateResult == GateResult.DENIED ) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean canReadValue(final Resource resource, final String valueName) {
+        // TODO Auto-generated method stub
+        return this.defaultAllow;
+    }
+
+    @Override
+    public boolean canSetValue(final Resource resource, final String valueName) {
+        // TODO Auto-generated method stub
+        return this.defaultAllow;
+    }
+
+    @Override
+    public boolean canDeleteValue(final Resource resource, final String valueName) {
+        // TODO Auto-generated method stub
+        return this.defaultAllow;
+    }
+
+    @Override
+    public String transformQuery(final String query,
+            final String language,
+            final ResourceResolver resourceResolver)
+    throws AccessSecurityException {
+        return query;
+    }
+
+    /**
+     * Add a new resource access gate
+     */
+    protected void bindResourceAccessGate(final ServiceReference ref) {
+        synchronized ( this ) {
+            final List<ResourceAccessGateHandler> newList = new ArrayList<ResourceAccessGateHandler>(this.allHandlers);
+
+            final ResourceAccessGateHandler h = new ResourceAccessGateHandler(ref);
+            newList.add(h);
+            Collections.sort(newList);
+            this.allHandlers = newList;
+        }
+    }
+
+    /**
+     * Remove a resource access gate
+     */
+    protected void unbindResourceAccessGate(final ServiceReference ref) {
+        synchronized ( this ) {
+            final List<ResourceAccessGateHandler> newList = new ArrayList<ResourceAccessGateHandler>(this.allHandlers);
+
+            final ResourceAccessGateHandler h = new ResourceAccessGateHandler(ref);
+            newList.remove(h);
+            this.allHandlers = newList;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/resourceaccesssecurity/package-info.java b/src/main/java/org/apache/sling/resourceaccesssecurity/package-info.java
new file mode 100644
index 0000000..61145aa
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceaccesssecurity/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.resourceaccesssecurity;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/test/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImplTests.java b/src/test/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImplTests.java
new file mode 100644
index 0000000..c7575a6
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceaccesssecurity/impl/ResourceAccessSecurityImplTests.java
@@ -0,0 +1,189 @@
+/*
+ * 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.resourceaccesssecurity.impl;
+
+
+import junit.framework.TestCase;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.security.ResourceAccessSecurity;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import static org.junit.Assert.assertTrue;
+
+public class ResourceAccessSecurityImplTests {
+
+    ServiceReference serviceReference;
+    ResourceAccessSecurity resourceAccessSecurity;
+    ResourceAccessGate resourceAccessGate;
+
+    @Before
+    public void setUp() {
+        resourceAccessSecurity = new ProviderResourceAccessSecurityImpl();
+    }
+
+
+    @Test
+    public void testCanUpdate(){
+        initMocks("/content", new String[] { "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        assertTrue(resourceAccessSecurity.canUpdate(resource));
+    }
+
+    @Test
+    public void testCannotUpdate(){
+        initMocks("/content", new String[] { "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.DENIED);
+        assertFalse(resourceAccessSecurity.canUpdate(resource));
+    }
+
+    @Test
+    public void testCannotUpdateWrongPath(){
+        initMocks("/content", new String[] { "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/wrongcontent");
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        assertFalse(resourceAccessSecurity.canUpdate(resource));
+    }
+
+    @Test
+    public void testCanUpdateUsingReadableResource(){
+        // one needs to have also read rights to obtain the resource
+
+        initMocks("/content", new String[] { "read", "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+
+        ModifiableValueMap valueMap = mock(ModifiableValueMap.class);
+        when(resource.adaptTo(ModifiableValueMap.class)).thenReturn(valueMap);
+
+        when(resourceAccessGate.canRead(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        Resource readableResource = resourceAccessSecurity.getReadableResource(resource);
+
+        ModifiableValueMap resultValueMap = readableResource.adaptTo(ModifiableValueMap.class);
+
+
+        resultValueMap.put("modified", "value");
+
+        verify(valueMap, times(1)).put("modified", "value");
+    }
+
+
+    @Test
+    public void testCannotUpdateUsingReadableResourceIfCannotRead(){
+        initMocks("/content", new String[] { "read", "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+
+        ModifiableValueMap valueMap = mock(ModifiableValueMap.class);
+        when(resource.adaptTo(ModifiableValueMap.class)).thenReturn(valueMap);
+
+        when(resourceAccessGate.canRead(resource)).thenReturn(ResourceAccessGate.GateResult.DENIED);
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        Resource readableResource = resourceAccessSecurity.getReadableResource(resource);
+
+
+        assertNull(readableResource);
+    }
+
+
+    @Test
+    public void testCannotUpdateUsingReadableResourceIfCannotUpdate(){
+        initMocks("/content", new String[] { "read", "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+
+        ModifiableValueMap valueMap = mock(ModifiableValueMap.class);
+        when(resource.adaptTo(ModifiableValueMap.class)).thenReturn(valueMap);
+
+        when(resourceAccessGate.canRead(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.DENIED);
+        Resource readableResource = resourceAccessSecurity.getReadableResource(resource);
+
+        ModifiableValueMap resultValueMap = readableResource.adaptTo(ModifiableValueMap.class);
+
+        assertNull(resultValueMap);
+    }
+
+
+    @Test
+    public void testCannotUpdateAccidentallyUsingReadableResourceIfCannotUpdate(){
+        initMocks("/content", new String[] { "read", "update"} );
+
+        Resource resource = mock(Resource.class);
+        when(resource.getPath()).thenReturn("/content");
+
+        ModifiableValueMap valueMap = mock(ModifiableValueMap.class);
+        when(resource.adaptTo(ModifiableValueMap.class)).thenReturn(valueMap);
+
+        when(resourceAccessGate.canRead(resource)).thenReturn(ResourceAccessGate.GateResult.GRANTED);
+        when(resourceAccessGate.canUpdate(resource)).thenReturn(ResourceAccessGate.GateResult.DENIED);
+        Resource readableResource = resourceAccessSecurity.getReadableResource(resource);
+
+        ValueMap resultValueMap = readableResource.adaptTo(ValueMap.class);
+
+        resultValueMap.put("modified", "value");
+
+        verify(valueMap, times(0)).put("modified", "value");
+    }
+
+    private void initMocks(String path, String[] operations){
+        serviceReference = mock(ServiceReference.class);
+        Bundle bundle = mock(Bundle.class);
+        BundleContext bundleContext = mock(BundleContext.class);
+        resourceAccessGate = mock(ResourceAccessGate.class);
+
+        when(serviceReference.getBundle()).thenReturn(bundle);
+        when(bundle.getBundleContext()).thenReturn(bundleContext);
+        when(bundleContext.getService(serviceReference)).thenReturn(resourceAccessGate);
+
+        when(serviceReference.getProperty(ResourceAccessGate.PATH)).thenReturn(path);
+        when(serviceReference.getProperty(ResourceAccessGate.OPERATIONS)).thenReturn(operations);
+
+        ((ProviderResourceAccessSecurityImpl) resourceAccessSecurity).bindResourceAccessGate(serviceReference);
+    }
+
+
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.