Skip to content

SLING-12739 - selectively hide scripts and servlets from the Sling resolver #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import org.apache.sling.api.SlingConstants;
import org.apache.sling.api.SlingHttpServletRequest;
Expand Down Expand Up @@ -68,6 +69,8 @@
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
Expand Down Expand Up @@ -132,6 +135,12 @@ public class SlingServletResolver implements ServletResolver, SlingRequestListen

private final ThreadLocal<ResourceResolver> perThreadScriptResolver = new ThreadLocal<>();

@Reference(
target = "(name=sling.servlet.resolver.resource.hiding)",
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.OPTIONAL)
private volatile Predicate<String> resourceHidingPredicate;

/**
* The allowed execution paths.
*/
Expand Down Expand Up @@ -460,6 +469,16 @@ private Resource getErrorResource(final SlingHttpServletRequest request) {
return res;
}

/** @return true if the given Resource is hidden by our resourceHidingPredicate */
private boolean isHidden(Resource r) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would explicitly mention that this method requires a non-null parameter (and that for this reason null-checks are missing).

Suggested change
private boolean isHidden(Resource r) {
private boolean isHidden(@NotNull Resource r) {

final boolean result =
r != null && resourceHidingPredicate != null && resourceHidingPredicate.test(r.getPath());
if (result && LOGGER.isDebugEnabled()) {
LOGGER.debug("Resource hidden by resource hiding predicate: {}", r.getPath());
}
return result;
}

/**
* Resolve an appropriate servlet for a given request and resource type
* using the provided ResourceResolver
Expand Down Expand Up @@ -564,6 +583,7 @@ private Servlet getServletInternal(

final Collection<Resource> candidates =
locationUtil.getServlets(resolver, localCache.getScriptEngineExtensions());
candidates.removeIf(r -> isHidden(r));

if (LOGGER.isDebugEnabled()) {
if (candidates.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.servlets.resolver.internal.resourcehiding;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.builder.Builders;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.servlets.resolver.internal.SlingServletResolverTestBase;
import org.apache.sling.servlets.resolver.internal.helper.HelperTestBase;
import org.apache.sling.servlets.resolver.internal.resource.MockServletResource;
import org.apache.sling.servlets.resolver.internal.resource.ServletResource;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.Bundle;

import static org.junit.Assert.fail;

public class ServletHidingTest extends SlingServletResolverTestBase {

private static final String SERVLET_EXTENSION = "html";
private static final String RESOURCE_TYPE =
ServletHidingTest.class.getSimpleName() + "/" + UUID.randomUUID().toString();

protected static class TestServlet extends HttpServlet {}
;

private void setServletHidingFilter(Predicate<String> predicate) throws Exception {
final Field predicateField = servletResolver.getClass().getDeclaredField("resourceHidingPredicate");
predicateField.setAccessible(true);
predicateField.set(servletResolver, predicate);
}

@Override
protected void defineTestServlets(Bundle bundle) {
final TestServlet testServlet = new TestServlet();
String path = "/" + RESOURCE_TYPE + "/" + ResourceUtil.getName(RESOURCE_TYPE) + ".servlet";
Map<String, Object> props = new HashMap<>();
props.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, path);
props.put("sling:resourceSuperType", ServletResource.DEFAULT_RESOURCE_SUPER_TYPE);
props.put(MockServletResource.PROP_SERVLET, testServlet);
HelperTestBase.addOrReplaceResource(mockResourceResolver, path, props);
try {
// commit so the resource is visible to the script resource resolver
// that is created later and can't see the temporary resources in
// this resource resolver
mockResourceResolver.commit();
} catch (PersistenceException e) {
fail("Did not expect a persistence exception: " + e.getMessage());
}
}

private void assertResolvesToTestServlet(String info, boolean expectMatch) {
final Resource resource = Mockito.mock(Resource.class);
Mockito.when(resource.getResourceType()).thenReturn(RESOURCE_TYPE);
Mockito.when(resource.getPath()).thenReturn("/" + RESOURCE_TYPE);

final SlingHttpServletRequest request = Builders.newRequestBuilder(resource)
.withExtension(SERVLET_EXTENSION)
.build();
final Servlet s = servletResolver.resolveServlet(request);
if (expectMatch != s.getClass().equals(TestServlet.class)) {
if (expectMatch) {
fail(info + ": expected to resolve to our test servlet, got "
+ s.getClass().getName());
} else {
fail(info + ": didn't expect to resolve to our test servlet");
}
}
}

@Test
public void testHideAndSeek() throws Exception {
final AtomicBoolean hide = new AtomicBoolean();
final Predicate<String> pred = (ignoredPath) -> hide.get();

// No filtering
setServletHidingFilter(null);
assertResolvesToTestServlet("before hiding", true);

// Filter with our predicate
setServletHidingFilter(pred);
hide.set(true);
assertResolvesToTestServlet("hidden by our Predicate", false);
hide.set(false);
assertResolvesToTestServlet("Predicate active but returns false", true);

// Back to no filtering, (paranoid) check that it's really gone
setServletHidingFilter(null);
hide.set(false);
assertResolvesToTestServlet("No Predicate set, hide=false", true);
hide.set(true);
assertResolvesToTestServlet("No Predicate set, hide=true", true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx
doGet(req, resp);
}

TestServlet with(String key, Object value) {
public TestServlet with(String key, Object value) {
properties.put(key, value);
return this;
}

void register(BundleContext context) {
public void register(BundleContext context) {
context.registerService(Servlet.class.getName(), this, properties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.servlets.resolver.it.resourcehiding;

import javax.servlet.http.HttpServletResponse;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;

import static org.junit.Assert.assertEquals;

@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class BasicResourceHidingIT extends ResourceHidingITBase {

@Before
public void setupPredicate() {
registerPredicate((path) -> path.contains(EXT_B));
}

@Test
public void testOnlyApresent() throws Exception {
assertEquals(0, hiddenResourcesCount);
assertTestServlet("/." + EXT_A, EXT_A);
assertEquals(0, hiddenResourcesCount);
assertTestServlet("/." + EXT_B, HttpServletResponse.SC_NOT_FOUND);
assertEquals(1, hiddenResourcesCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.servlets.resolver.it.resourcehiding;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;

import static org.junit.Assert.assertEquals;

@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class HiddenServletFallbackIT extends ResourceHidingITBase {

@Before
public void setupPredicate() {
registerPredicate((path) -> path.contains(SEL_A));
}

@Test
public void testFallbackToExtA() throws Exception {
assertEquals(0, hiddenResourcesCount);
assertTestServlet("/." + SEL_A + "." + EXT_A, EXT_A);
assertEquals(1, hiddenResourcesCount);
assertTestServlet("/." + EXT_A, EXT_A);
assertEquals(1, hiddenResourcesCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.servlets.resolver.it.resourcehiding;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;

import static org.junit.Assert.assertEquals;

@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class NoHidingIT extends ResourceHidingITBase {

@After
public void checkNothingHidden() {
assertEquals(0, hiddenResourcesCount);
}

@Test
public void testExtApresent() throws Exception {
assertTestServlet("/." + EXT_A, EXT_A);
}

@Test
public void testExtBpresent() throws Exception {
assertTestServlet("/." + EXT_B, EXT_B);
}

@Test
public void testSelApresent() throws Exception {
assertTestServlet("/." + SEL_A + "." + EXT_A, SEL_A);
}
}
Loading