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 <[email protected]> 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 "[email protected]" <[email protected]>.
