blob: 380be0a7d58d7e2d3e26090a48aa66cade796682 [file] [log] [blame]
/*
* 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.resourceresolver.impl;
import static java.util.Arrays.asList;
import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
import static org.apache.sling.resourceresolver.util.MockTestUtil.buildResource;
import static org.apache.sling.resourceresolver.util.MockTestUtil.callInaccessibleMethod;
import static org.apache.sling.resourceresolver.util.MockTestUtil.checkInternalResource;
import static org.apache.sling.resourceresolver.util.MockTestUtil.checkRedirectResource;
import static org.apache.sling.resourceresolver.util.MockTestUtil.createRequestFromUrl;
import static org.apache.sling.resourceresolver.util.MockTestUtil.createStringInterpolationProviderConfiguration;
import static org.apache.sling.resourceresolver.util.MockTestUtil.setInaccessibleField;
import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.observation.ResourceChange;
import org.apache.sling.api.resource.path.Path;
import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider;
import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider;
import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
import org.apache.sling.resourceresolver.util.MockTestUtil;
import org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
import org.apache.sling.serviceusermapping.ServiceUserMapper;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.EventAdmin;
/**
* These are the same tests as in the EtcMappingMapEntriesTest but in this
* class we are actually mocking the Resource Resolver Factory and its classes
* and we test the mapping and resource resolution through the resource resolver
* rather the MapEntries.
*/
public class EtcMappingResourceResolverTest {
static final String PROP_REG_EXP = "sling:match";
@Mock
ResourceResolverFactory resourceResolverFactory;
@Mock
BundleContext bundleContext;
@Mock
Bundle bundle;
@Mock
EventAdmin eventAdmin;
@Mock
ResourceResolver resourceResolver;
@Mock
ResourceProvider<?> resourceProvider;
StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
StringInterpolationProvider stringInterpolationProvider = new StringInterpolationProviderImpl();
MapEntries mapEntries;
File vanityBloomFilterFile;
CommonResourceResolverFactoryImpl commonFactory;
Resource etc;
Resource map;
Resource http;
Map<String, Map<String, String>> aliasMap;
@SuppressWarnings({"unchecked"})
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
List<MapConfigurationProvider.VanityPathConfig> configs = getVanityPathConfigs();
vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
List<ResourceProviderHandler> handlers = asList(createRPHandler(resourceProvider, "rp1", 0, "/"));
ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class);
ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
ResourceResolverFactoryActivator activator = new ResourceResolverFactoryActivator();
// These fields on the Activator a package private so we need reflection to access them
setInaccessibleField("resourceProviderTracker", activator, resourceProviderTracker);
setInaccessibleField("resourceAccessSecurityTracker", activator, new ResourceAccessSecurityTracker());
setInaccessibleField("bundleContext", activator, bundleContext);
stringInterpolationProviderConfiguration = createStringInterpolationProviderConfiguration();
setInaccessibleField("stringInterpolationProvider", activator, stringInterpolationProvider);
setInaccessibleField("mapRoot", activator, "/etc/map");
setInaccessibleField("mapRootPrefix", activator, "/etc/map");
setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class);
setInaccessibleField("serviceUserMapper", activator, serviceUserMapper);
setInaccessibleField("pathToUriMappingService", activator, MockTestUtil.createPathToUriMappingServiceMock(resourceResolver));
commonFactory = spy(new CommonResourceResolverFactoryImpl(activator));
when(bundleContext.getBundle()).thenReturn(bundle);
when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
when(serviceUserMapper.getServiceUserID(any(Bundle.class),anyString())).thenReturn("mapping");
// Activate method is package private so we use reflection to to call it
callInaccessibleMethod("activate", null, commonFactory, BundleContext.class, bundleContext);
final Bundle usingBundle = mock(Bundle.class);
resourceResolverFactory = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
etc = buildResource("/etc", null, resourceResolver, resourceProvider);
map = buildResource("/etc/map", etc, resourceResolver, resourceProvider);
http = buildResource("/etc/map/http", map, resourceResolver, resourceProvider);
}
List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
return new ArrayList<>();
}
/**
* Changes to the /etc/map in our tests are not taking effect until there is an Change Event issued
*
* ATTENTION: this method can only be issued once. After that the Resource Metadata is locked and
* hence updates will fail
*
* @param path Path to the resource root to be refreshed
* @param isExternal External flag of the ResourceChange event
*/
void refreshMapEntries(String path, boolean isExternal) {
((MapEntries) commonFactory.getMapEntries()).onChange(
asList(
new ResourceChange(ResourceChange.ChangeType.ADDED, path, isExternal)
)
);
}
@Test
public void root_node_to_content_mapping() throws Exception {
buildResource(http.getPath() + "/localhost.8080", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "/content/simple-node");
// This updates the map entries so that the newly added resources are added.
// ATTENTION: only call this after all etc-mapping resources are defined as this lock their Resource Meta Data and prevents a re-update
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-node/");
expectedEtcMapping.assertEtcMap("Etc Mapping for root node to content", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "/content/simple-node/", 302);
}
@Test
public void match_to_content_mapping() throws Exception {
buildResource("test-node", http, resourceResolver, resourceProvider,
PROP_REG_EXP, "localhost.8080/",
PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
);
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-match/");
expectedEtcMapping.assertEtcMap("Etc Mapping for match to content", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
}
// The following tests are based on the example from the https://sling.apache.org/documentation/the-sling-engine/mappings-for-resource-resolution.html page
@Test
public void internal_to_external_node_mapping() throws Exception {
buildResource("example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "http://www.example.com/");
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/example.com.80/", "http://www.example.com/");
expectedEtcMapping.assertEtcMap("Etc Mapping for internal to external based on node", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://example.com/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "http://www.example.com/", 302);
}
@Test
public void internal_root_to_content_node_mapping() throws Exception {
buildResource("/example", null, resourceResolver, resourceProvider);
buildResource("www.example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_INTERNAL, "/example");
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/www.example.com.80/", true, "/example/");
expectedEtcMapping.assertEtcMap("Etc Mapping for internal root to content", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://www.example.com:80/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkInternalResource(resolvedResource, "/example");
}
@Test
public void host_redirect_match_mapping() throws Exception {
buildResource("any_example.com.80", http, resourceResolver, resourceProvider,
PROP_REG_EXP, ".+\\.example\\.com\\.80",
PROP_REDIRECT_EXTERNAL, "http://www.example.com/"
);
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/.+\\.example\\.com\\.80", false, "http://www.example.com/");
expectedEtcMapping.assertEtcMap("Etc Mapping for host redirect match mapping", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://www.example.com");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "http://www.example.com//", 302);
}
@Test
public void nested_internal_mixed_mapping() throws Exception {
Resource localhost = buildResource("localhost_any", http, resourceResolver, resourceProvider,
PROP_REG_EXP, "localhost\\.\\d*",
PROP_REDIRECT_INTERNAL, "/content"
);
buildResource("cgi-bin", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/scripts");
buildResource("gateway", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "http://gbiv.com");
buildResource("(stories)", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/anecdotes/$1");
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping()
.addEtcMapEntry("^http/localhost\\.\\d*", true, "/content")
.addEtcMapEntry("^http/localhost\\.\\d*/cgi-bin/", true, "/scripts/")
.addEtcMapEntry("^http/localhost\\.\\d*/gateway/", true, "http://gbiv.com/")
.addEtcMapEntry("^http/localhost\\.\\d*/(stories)/", true, "/anecdotes/$1/");
expectedEtcMapping.assertEtcMap("Etc Mapping for nested internal mixed mapping", commonFactory.getMapEntries().getResolveMaps());
buildResource("/content", null, resourceResolver, resourceProvider);
Resource scripts = buildResource("/scripts", null, resourceResolver, resourceProvider);
Resource scriptsChild = buildResource("/scripts/child", scripts, resourceResolver, resourceProvider);
Resource anecdotes = buildResource("/anecdotes", null, resourceResolver, resourceProvider);
Resource stories = buildResource("/anecdotes/stories", anecdotes, resourceResolver, resourceProvider);
HttpServletRequest request = createRequestFromUrl("http://localhost:1234/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkInternalResource(resolvedResource, "/content");
resolvedResource = resourceResolver.resolve(request, "/cgi-bin/");
checkInternalResource(resolvedResource, "/scripts");
resolvedResource = resourceResolver.resolve(request, "/cgi-bin/child/");
checkInternalResource(resolvedResource, "/scripts/child");
//AS TODO: Does not redirect -> investigate later
// resolvedResource = resourceResolver.resolve(request, "/gateway/");
// checkRedirectResource(resolvedResource, "http://gbiv.com/", 302);
resolvedResource = resourceResolver.resolve(request, "/stories/");
checkInternalResource(resolvedResource, "/anecdotes/stories");
}
@Test
public void simple_node_string_interpolation() throws Exception {
buildResource("$[config:siv.one]", http, resourceResolver, resourceProvider,PROP_REDIRECT_EXTERNAL, "/content/simple-node");
setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-node.80"});
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-node.80/", "/content/simple-node/");
expectedEtcMapping.assertEtcMap("String Interpolation for simple match", commonFactory.getMapEntries().getResolveMaps());
Resource content = buildResource("/content", null, resourceResolver, resourceProvider);
Resource simpleNode = buildResource("/content/simple-node", content, resourceResolver, resourceProvider);
HttpServletRequest request = createRequestFromUrl("http://test-simple-node:80/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "/content/simple-node/", 302);
}
@Test
public void simple_match_string_interpolation() throws Exception {
buildResource("test-node", http, resourceResolver, resourceProvider,
PROP_REG_EXP, "$[config:siv.one]/",
PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
);
setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-match.80"});
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match.80/", "/content/simple-match/");
expectedEtcMapping.assertEtcMap("String Interpolation for simple match", commonFactory.getMapEntries().getResolveMaps());
HttpServletRequest request = createRequestFromUrl("http://test-simple-match:80/");
Resource resolvedResource = resourceResolver.resolve(request, "/");
checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
}
/**
* ATTENTION: this tests showcases an erroneous condition of an endless circular mapping in the /etc/map. When
* this test passes this condition is present. After a fix this test must be adjusted.
*
* This confirms an issue with the Etc Mapping where a mapping from a node to a child node (here / to /content)
* ends up in a endless circular mapping.
* The only way to recover from this is to go to the OSGi console and change the /etc/map path in the Resource
* Resolver factory.
* Either the Etc Mapping discovers this condition and stops it or at least ignores mapping for Composum to allow
* the /etc/map to be edited.
*/
@Test
public void endless_circular_mapping() throws Exception {
buildResource(http.getPath() + "/localhost.8080", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "/content");
refreshMapEntries("/etc/map", true);
ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/");
expectedEtcMapping.assertEtcMap("Etc Mapping for root node to content", commonFactory.getMapEntries().getResolveMaps());
buildResource("/content/test", null, resourceResolver, resourceProvider);
buildResource("/content/content/test", null, resourceResolver, resourceProvider);
buildResource("/content/content/content/test", null, resourceResolver, resourceProvider);
HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
Resource resolvedResource = resourceResolver.resolve(request, "/test.html");
checkRedirectResource(resolvedResource, "/content/test.html", 302);
resolvedResource = resourceResolver.resolve(request, "/content/test.html");
checkRedirectResource(resolvedResource, "/content/content/test.html", 302);
resolvedResource = resourceResolver.resolve(request, "/content/content/test.html");
checkRedirectResource(resolvedResource, "/content/content/content/test.html", 302);
}
}