diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2020-11-11 16:23:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-11 16:23:46 +0100 |
commit | 244f51c38338b66ff2f7b01dbef268f76aa3549d (patch) | |
tree | c90b7af9cc5448d74576b175a47efba42e414b2c /container-disc | |
parent | 3d743e2ab68e2060312d79a8960272f81b36db0d (diff) |
Revert "Remove implicit and potensially false dependency on jdisc_http_service"
Diffstat (limited to 'container-disc')
4 files changed, 370 insertions, 7 deletions
diff --git a/container-disc/pom.xml b/container-disc/pom.xml index 4a3936b870d..1caf66e29dc 100644 --- a/container-disc/pom.xml +++ b/container-disc/pom.xml @@ -37,13 +37,7 @@ <version>${project.version}</version> <scope>provided</scope> </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>jdisc_http_service</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - <dependency> + <dependency> <groupId>com.yahoo.vespa</groupId> <artifactId>provided-dependencies</artifactId> <version>${project.version}</version> diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java new file mode 100644 index 00000000000..972f9dd8e18 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java @@ -0,0 +1,62 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.http.ServerConfig; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.server.FilterBindings; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides filter bindings based on vespa config. + * + * @author bakksjo + */ +public class FilterBindingsProvider implements Provider<FilterBindings> { + + final BindingRepository<RequestFilter> requestFilters = new BindingRepository<>(); + final BindingRepository<ResponseFilter> responseFilters = new BindingRepository<>(); + + public FilterBindingsProvider(ComponentId componentId, + ServerConfig config, + FilterChainRepository filterChainRepository, + ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + ComponentId serverId = componentId.getNamespace(); + try { + FilterUtil.setupFilters( + componentId, + legacyRequestFilters, + toFilterSpecs(config.filter()), + filterChainRepository, + requestFilters, + responseFilters); + } catch (Exception e) { + throw new RuntimeException("Invalid config for http server " + serverId, e); + } + } + + private List<FilterUtil.FilterSpec> toFilterSpecs(List<ServerConfig.Filter> inFilters) { + List<FilterUtil.FilterSpec> outFilters = new ArrayList<>(); + for (ServerConfig.Filter inFilter : inFilters) { + outFilters.add(new FilterUtil.FilterSpec(inFilter.id(), inFilter.binding())); + } + return outFilters; + } + + @Override + public FilterBindings get() { + return new FilterBindings(requestFilters, responseFilters); + } + + @Override + public void deconstruct() {} + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java new file mode 100644 index 00000000000..24af56788b9 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java @@ -0,0 +1,105 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.UriPattern; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain; + +import java.util.List; + +/** + * Helper class to set up filter binding repositories based on config. + * + * @author Øyvind Bakksjø + */ +class FilterUtil { + + private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer"); + + private final BindingRepository<RequestFilter> requestFilters; + private final BindingRepository<ResponseFilter> responseFilters; + + private FilterUtil(BindingRepository<RequestFilter> requestFilters, BindingRepository<ResponseFilter> responseFilters) { + this.requestFilters = requestFilters; + this.responseFilters = responseFilters; + } + + private void configureFilters(List<FilterSpec> filtersConfig, FilterChainRepository filterChainRepository) { + for (FilterSpec filterConfig : filtersConfig) { + Object filter = filterChainRepository.getFilter(ComponentSpecification.fromString(filterConfig.getId())); + if (filter == null) { + throw new RuntimeException("No http filter with id " + filterConfig.getId()); + } + addFilter(filter, filterConfig.getBinding()); + } + } + + private void addFilter(Object filter, String binding) { + if (filter instanceof RequestFilter && filter instanceof ResponseFilter) { + throw new RuntimeException("The filter " + filter.getClass().getName() + + " is unsupported since it's both a RequestFilter and a ResponseFilter."); + } else if (filter instanceof RequestFilter) { + requestFilters.put(new UriPattern(binding), (RequestFilter) filter); + } else if (filter instanceof ResponseFilter) { + responseFilters.put(new UriPattern(binding), (ResponseFilter) filter); + } else { + throw new RuntimeException("Unknown filter type " + filter.getClass().getName()); + } + } + + //TVT: remove + private void configureLegacyFilters(ComponentId id, ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + ComponentId serverName = id.getNamespace(); + if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) { + requestFilters.bind("http://*/*", + SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents())); + } + } + + /** + * Populates binding repositories with filters based on config. + * + * @param requestFilters output argument that will be mutated + * @param responseFilters output argument that will be mutated + */ + public static void setupFilters(ComponentId componentId, + ComponentRegistry<SecurityRequestFilter> legacyRequestFilters, + List<FilterSpec> filtersConfig, + FilterChainRepository filterChainRepository, + BindingRepository<RequestFilter> requestFilters, + BindingRepository<ResponseFilter> responseFilters) { + FilterUtil filterUtil = new FilterUtil(requestFilters, responseFilters); + + // TODO: remove + filterUtil.configureLegacyFilters(componentId, legacyRequestFilters); + + filterUtil.configureFilters(filtersConfig, filterChainRepository); + } + + public static class FilterSpec { + + private final String id; + private final String binding; + + public FilterSpec(String id, String binding) { + this.id = id; + this.binding = binding; + } + + public String getId() { + return id; + } + + public String getBinding() { + return binding; + } + } + +} diff --git a/container-disc/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java b/container-disc/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java new file mode 100644 index 00000000000..e3039e88525 --- /dev/null +++ b/container-disc/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java @@ -0,0 +1,202 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.core.ChainsConfig; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.UriPattern; +import com.yahoo.jdisc.http.ServerConfig; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.server.FilterBindings; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +/** + * @author bakksjo + */ +public class FilterBindingsProviderTest { + final ServerConfig.Builder configBuilder = new ServerConfig.Builder(); + + @Test + public void requireThatEmptyInputGivesEmptyOutput() { + final FilterChainRepository filterChainRepository = new FilterChainRepository( + new ChainsConfig(new ChainsConfig.Builder()), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>()); + + final FilterBindingsProvider provider = new FilterBindingsProvider( + new ComponentId("foo"), + new ServerConfig(configBuilder), + filterChainRepository, + new ComponentRegistry<>()); + + final FilterBindings filterBindings = provider.get(); + + assertNotNull(filterBindings); + assertFalse(filterBindings.getRequestFilters().iterator().hasNext()); + assertFalse(filterBindings.getResponseFilters().iterator().hasNext()); + } + + @Test + public void requireThatCorrectlyConfiguredFiltersAreIncluded() { + final String requestFilter1Id = "requestFilter1"; + final String requestFilter2Id = "requestFilter2"; + final String requestFilter3Id = "requestFilter3"; + final String responseFilter1Id = "responseFilter1"; + final String responseFilter2Id = "responseFilter2"; + final String responseFilter3Id = "responseFilter3"; + + // Set up config. + configBuilder.filter(new ServerConfig.Filter.Builder().id(requestFilter1Id).binding("http://*/a")); + configBuilder.filter(new ServerConfig.Filter.Builder().id(requestFilter2Id).binding("http://*/b")); + configBuilder.filter(new ServerConfig.Filter.Builder().id(responseFilter1Id).binding("http://*/c")); + configBuilder.filter(new ServerConfig.Filter.Builder().id(responseFilter3Id).binding("http://*/d")); + + // Set up registry. + final ComponentRegistry<RequestFilter> availableRequestFilters = new ComponentRegistry<>(); + final RequestFilter requestFilter1Instance = mock(RequestFilter.class); + final RequestFilter requestFilter2Instance = mock(RequestFilter.class); + final RequestFilter requestFilter3Instance = mock(RequestFilter.class); + availableRequestFilters.register(ComponentId.fromString(requestFilter1Id), requestFilter1Instance); + availableRequestFilters.register(ComponentId.fromString(requestFilter2Id), requestFilter2Instance); + availableRequestFilters.register(ComponentId.fromString(requestFilter3Id), requestFilter3Instance); + final ComponentRegistry<ResponseFilter> availableResponseFilters = new ComponentRegistry<>(); + final ResponseFilter responseFilter1Instance = mock(ResponseFilter.class); + final ResponseFilter responseFilter2Instance = mock(ResponseFilter.class); + final ResponseFilter responseFilter3Instance = mock(ResponseFilter.class); + availableResponseFilters.register(ComponentId.fromString(responseFilter1Id), responseFilter1Instance); + availableResponseFilters.register(ComponentId.fromString(responseFilter2Id), responseFilter2Instance); + availableResponseFilters.register(ComponentId.fromString(responseFilter3Id), responseFilter3Instance); + final FilterChainRepository filterChainRepository = new FilterChainRepository( + new ChainsConfig(new ChainsConfig.Builder()), + availableRequestFilters, + availableResponseFilters, + new ComponentRegistry<>(), + new ComponentRegistry<>()); + + // Set up the provider that we aim to test. + final FilterBindingsProvider provider = new FilterBindingsProvider( + new ComponentId("foo"), + new ServerConfig(configBuilder), + filterChainRepository, + new ComponentRegistry<>()); + + // Execute. + final FilterBindings filterBindings = provider.get(); + + // Verify. + assertNotNull(filterBindings); + assertThat( + filterBindings.getRequestFilters(), + containsFilters(requestFilter1Instance, requestFilter2Instance)); + assertThat( + filterBindings.getRequestFilters(), + not(containsFilters(requestFilter3Instance))); + assertThat( + filterBindings.getResponseFilters(), + containsFilters(responseFilter1Instance, responseFilter3Instance)); + assertThat( + filterBindings.getResponseFilters(), + not(containsFilters(responseFilter2Instance))); + } + + private interface DualRoleFilter extends RequestFilter, ResponseFilter {} + + @Test + public void requireThatInstanceCanNotBeBothRequestAndResponseFilter() { + final String filterId = "filter"; + + // Set up config. + configBuilder.filter(new ServerConfig.Filter.Builder().id(filterId).binding("http://*/*")); + + // Set up registry. + final DualRoleFilter filterInstance = mock(DualRoleFilter.class); + final ComponentRegistry<RequestFilter> availableRequestFilters = new ComponentRegistry<>(); + availableRequestFilters.register(ComponentId.fromString(filterId), filterInstance); + final FilterChainRepository filterChainRepository = new FilterChainRepository( + new ChainsConfig(new ChainsConfig.Builder()), + availableRequestFilters, + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>()); + + try { + new FilterBindingsProvider( + new ComponentId("foo"), + new ServerConfig(configBuilder), + filterChainRepository, + new ComponentRegistry<>()); + fail("Dual-role filter should not be accepted"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Invalid config")); + } + } + + @Test + public void requireThatConfigWithUnknownReferenceFails() { + // Set up config. + configBuilder.filter(new ServerConfig.Filter.Builder().id("someFilter").binding("http://*/*")); + + // Set up registry. + final FilterChainRepository filterChainRepository = new FilterChainRepository( + new ChainsConfig(new ChainsConfig.Builder()), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>()); + + try { + new FilterBindingsProvider( + new ComponentId("foo"), + new ServerConfig(configBuilder), + filterChainRepository, + new ComponentRegistry<>()); + fail("Config with unknown filter reference should not be accepted"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Invalid config")); + } + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static <T> Matcher<? super BindingRepository<T>> containsFilters( + final T... requiredInstances) { + return new TypeSafeMatcher<>() { + private final Set<T> requiredFilterSet = new HashSet<>(Arrays.asList(requiredInstances)); + + @Override + protected boolean matchesSafely(final BindingRepository<T> actualInstances) { + final Set<T> notFoundFilterSet = new HashSet<>(requiredFilterSet); + for (final Map.Entry<UriPattern, T> actualEntry : actualInstances) { + notFoundFilterSet.remove(actualEntry.getValue()); + } + return notFoundFilterSet.isEmpty(); + } + + @Override + public void describeTo(final Description description) { + description.appendText("BindingRepository containing " + requiredFilterSet); + } + }; + } +} |