This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-security.git
The following commit(s) were added to refs/heads/master by this push:
new ed34399 SLING-9061 - Adding support for evaluating the origin as
well as the referrer and pluggable referrer configurations (#9)
ed34399 is described below
commit ed343999a43e709000b239d57065d1fb89e7c832
Author: Dan Klco <[email protected]>
AuthorDate: Thu May 18 21:19:35 2023 -0400
SLING-9061 - Adding support for evaluating the origin as well as the
referrer and pluggable referrer configurations (#9)
* SLING-11871 - Adding support for bypassing the ReferrerFilter for
requests with an Origin header
* SLING-9061 - Adding support for evaluating the origin as well as the
referrer and pluggable referrer configurations
* Changing mergeValues to return a collection rather an an array
---
.../apache/sling/security/impl/ReferrerFilter.java | 41 ++++++++--
.../security/impl/ReferrerFilterAmendment.java | 43 +++++++++++
.../security/impl/ReferrerFilterAmendmentImpl.java | 90 ++++++++++++++++++++++
.../sling/security/impl/ReferrerFilterTest.java | 87 +++++++++++++++++----
4 files changed, 237 insertions(+), 24 deletions(-)
diff --git a/src/main/java/org/apache/sling/security/impl/ReferrerFilter.java
b/src/main/java/org/apache/sling/security/impl/ReferrerFilter.java
index 5125e57..f563234 100644
--- a/src/main/java/org/apache/sling/security/impl/ReferrerFilter.java
+++ b/src/main/java/org/apache/sling/security/impl/ReferrerFilter.java
@@ -27,11 +27,14 @@ import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Pattern;
+
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@@ -42,6 +45,9 @@ import javax.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
import org.osgi.service.http.whiteboard.Preprocessor;
import org.osgi.service.metatype.annotations.AttributeDefinition;
@@ -245,7 +251,7 @@ public class ReferrerFilter implements Preprocessor {
/**
* Create Patterns out of the regular expression referrer list
*/
- private Pattern[] createRegexPatterns(final String[] regexps) {
+ private Pattern[] createRegexPatterns(final Collection<String> regexps) {
final List<Pattern> patterns = new ArrayList<>();
if (regexps != null) {
for (final String regexp : regexps) {
@@ -260,16 +266,32 @@ public class ReferrerFilter implements Preprocessor {
return patterns.toArray(new Pattern[0]);
}
+ private Collection<String> mergeValues(String[] primary,
List<ReferrerFilterAmendment> amendments,
+ Function<ReferrerFilterAmendment, String[]> extractor) {
+ Set<String> consolidated = new HashSet<>();
+ if (primary != null) {
+ Arrays.stream(primary).forEach(consolidated::add);
+ }
+ if (amendments != null) {
+ amendments.stream().map(extractor::apply).forEach(v ->
Arrays.stream(v).forEach(consolidated::add));
+ }
+ return consolidated;
+ }
+
@Activate
- public ReferrerFilter(final Config config) {
+ public ReferrerFilter(final Config config,
+ @Reference(policyOption = ReferencePolicyOption.GREEDY,
cardinality = ReferenceCardinality.MULTIPLE,
service=ReferrerFilterAmendment.class) List<ReferrerFilterAmendment>
amendments) {
this.allowEmpty = config.allow_empty();
- this.allowedRegexReferrers =
createRegexPatterns(config.allow_hosts_regexp());
- this.excludedRegexUserAgents =
createRegexPatterns(config.exclude_agents_regexp());
- this.excludedPaths = config.exclude_paths();
+ this.allowedRegexReferrers = createRegexPatterns(
+ mergeValues(config.allow_hosts_regexp(), amendments, a ->
a.allowHostsRegex()));
+ this.excludedRegexUserAgents = createRegexPatterns(
+ mergeValues(config.exclude_agents_regexp(), amendments, a ->
a.excludeAgentsRegex()));
+ this.excludedPaths = mergeValues(config.exclude_paths(), amendments, a
-> a.excludePaths()).toArray(new String[0]);
final Set<String> allowUriReferrers = getDefaultAllowedReferrers();
if (config.allow_hosts() != null) {
- allowUriReferrers.addAll(Arrays.asList(config.allow_hosts()));
+ allowUriReferrers.addAll(
+ mergeValues(config.allow_hosts(), amendments, a ->
a.allowHosts()));
}
this.allowedUriReferrers = createReferrerUrls(allowUriReferrers);
@@ -368,7 +390,12 @@ public class ReferrerFilter implements Preprocessor {
return true;
}
- final String referrer = request.getHeader("referer");
+ String referrer = request.getHeader("referer");
+ // use the origin if the referrer is not set
+ if (referrer == null || referrer.trim().length() == 0) {
+ referrer = request.getHeader("origin");
+ }
+
// check for missing/empty referrer
if (referrer == null || referrer.trim().length() == 0) {
if (!this.allowEmpty) {
diff --git
a/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendment.java
b/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendment.java
new file mode 100644
index 0000000..09f6fcb
--- /dev/null
+++ b/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendment.java
@@ -0,0 +1,43 @@
+/*
+ * 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.security.impl;
+
+/**
+ * Amends the primary configuration of Referrer Filter
+ */
+public interface ReferrerFilterAmendment {
+ /**
+ * @return List of allowed hosts for the referrer which are added to the
list of
+ * default hosts.
+ */
+ String[] allowHosts();
+
+ /**
+ * @return List of allowed regular expression for the referrer.
+ */
+ String[] allowHostsRegex();
+
+ /**
+ * @return List of regexp for user agents not to check the referrer
+ */
+ String[] excludeAgentsRegex();
+
+ /**
+ * @return List of paths for which not to check the referrer
+ */
+ String[] excludePaths();
+}
diff --git
a/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendmentImpl.java
b/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendmentImpl.java
new file mode 100644
index 0000000..4bdcb43
--- /dev/null
+++
b/src/main/java/org/apache/sling/security/impl/ReferrerFilterAmendmentImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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.security.impl;
+
+import java.util.Optional;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(service = ReferrerFilterAmendment.class)
+@Designate(factory = true, ocd = ReferrerFilterAmendmentImpl.Config.class)
+public class ReferrerFilterAmendmentImpl implements ReferrerFilterAmendment {
+
+ private final Config config;
+
+ @Activate
+ public ReferrerFilterAmendmentImpl(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ public String[] allowHosts() {
+ return Optional.ofNullable(config.allow_hosts()).orElse(new String[0]);
+ }
+
+ @Override
+ public String[] allowHostsRegex() {
+ return Optional.ofNullable(config.allow_hosts_regexp()).orElse(new
String[0]);
+ }
+
+ @Override
+ public String[] excludeAgentsRegex() {
+ return Optional.ofNullable(config.exclude_agents_regexp()).orElse(new
String[0]);
+ }
+
+ @Override
+ public String[] excludePaths() {
+ return Optional.ofNullable(config.exclude_paths()).orElse(new
String[0]);
+ }
+
+ @ObjectClassDefinition(name = "Apache Sling Referrer Filter Amendment",
description = "Amend the primary list of Referrer Filter allow hosts with
additional hosts")
+ public @interface Config {
+
+ @AttributeDefinition(name = "Allow Hosts", description = "List of
allowed hosts for the referrer which are added to the list of default hosts. "
+ + "It is matched against the full referrer URL in the format
\"<scheme>://<host>:<port>\". "
+ + "If port is 0, it is not taken into consideration. The
default list contains all host names "
+ + "and IPs bound to all NICs found in the system plus
\"localhost\", \"127.0.0.1\", \"[::1]\" for protocols \"http\" and \"https\". "
+ + "If given value does not have a \":\" entries for both http
and https are transparently generated.")
+ String[] allow_hosts() default {};
+
+ /**
+ * Allow referrer regex hosts property
+ */
+ @AttributeDefinition(name = "Allow Regexp Host", description = "List
of allowed regular expression for the referrer. "
+ + "It is matched against the full referrer URL in the format
\"<scheme>://<host>:<port>\". "
+ + "Evaluated in addition to the default list and the given
allowed hosts (see above)!")
+ String[] allow_hosts_regexp() default {};
+
+ /**
+ * Excluded regexp user agents property
+ */
+ @AttributeDefinition(name = "Exclude Regexp User Agent", description =
"List of regexp for user agents not to check the referrer")
+ String[] exclude_agents_regexp() default {};
+
+ /**
+ * Excluded the configured paths from the referrer check
+ */
+ @AttributeDefinition(name = "Exclude Paths", description = "List of
paths for which not to check the referrer")
+ String[] exclude_paths() default {};
+
+ }
+
+}
\ No newline at end of file
diff --git
a/src/test/java/org/apache/sling/security/impl/ReferrerFilterTest.java
b/src/test/java/org/apache/sling/security/impl/ReferrerFilterTest.java
index 53b1f29..0a68898 100644
--- a/src/test/java/org/apache/sling/security/impl/ReferrerFilterTest.java
+++ b/src/test/java/org/apache/sling/security/impl/ReferrerFilterTest.java
@@ -22,9 +22,11 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.annotation.Annotation;
+import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
+import org.apache.sling.security.impl.ReferrerFilterAmendmentImpl.Config;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -35,15 +37,16 @@ public class ReferrerFilterTest {
@Before
public void setup() {
- ReferrerFilter.Config config = createConfiguration(false, new
String[]{"relhost"},
- new String[]{"http://([^.]*.)?abshost:80", "^app://.+"},
- new String[]{"[a-zA-Z]*\\/[0-9]*\\.[0-9]*;Some-Agent\\s.*"},
- new String[] {null, "/test_path"});
- filter = new ReferrerFilter(config);
+ ReferrerFilter.Config config = createConfiguration(false, new String[]
{ "relhost" },
+ new String[] { "http://([^.]*.)?abshost:80", "^app://.+" },
+ new String[] { "[a-zA-Z]*\\/[0-9]*\\.[0-9]*;Some-Agent\\s.*" },
+ new String[] { null, "/test_path" });
+ filter = new ReferrerFilter(config, Collections.emptyList());
}
- private static ReferrerFilter.Config createConfiguration(boolean
allowEmpty, String[] allowHosts, String[] allowHostsRexexp,
- String[]
excludeAgentsRegexp, String[] excludePaths) {
+ private static ReferrerFilter.Config createConfiguration(boolean
allowEmpty, String[] allowHosts,
+ String[] allowHostsRexexp,
+ String[] excludeAgentsRegexp, String[] excludePaths) {
return new ReferrerFilter.Config() {
@Override
public Class<? extends Annotation> annotationType() {
@@ -79,7 +82,7 @@ public class ReferrerFilterTest {
public String[] exclude_paths() {
return excludePaths;
}
- };
+ };
}
@Test
@@ -105,18 +108,18 @@ public class ReferrerFilterTest {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getMethod()).thenReturn("POST");
if (pathInfo != null) {
-
when(request.getRequestURI()).thenReturn("http://somehost/somewhere"+pathInfo);
+
when(request.getRequestURI()).thenReturn("http://somehost/somewhere" +
pathInfo);
when(request.getPathInfo()).thenReturn(pathInfo);
} else {
when(request.getRequestURI()).thenReturn("http://somehost/somewhere");
}
when(request.getHeader("referer")).thenReturn(referrer);
- if ( userAgent != null && userAgent.length() > 0 ) {
+ if (userAgent != null && userAgent.length() > 0) {
when(request.getHeader("User-Agent")).thenReturn(userAgent);
}
return request;
}
-
+
private static HttpServletRequest getRequest(final String referrer, final
String userAgent) {
return getRequest(referrer, userAgent, null);
}
@@ -146,21 +149,22 @@ public class ReferrerFilterTest {
assertTrue(filter.isValidRequest(getRequest("app://yet.another.abshost:80")));
assertFalse(filter.isValidRequest(getRequest("?://")));
}
-
+
@Test
public void testExcludedPath() {
assertTrue(filter.isValidRequest(getRequest(null, null,
"/test_path")));
assertFalse(filter.isValidRequest(getRequest(null, null,
"/test_path/subtree")));
assertFalse(filter.isValidRequest(getRequest(null, null,
"/test_path_sibling")));
-
+
assertTrue(filter.isValidRequest(getRequest("relative", null,
"/test_path")));
assertTrue(filter.isValidRequest(getRequest("http://yet.another.abshost:80",
null, "/test_path")));
}
@Test
public void testExcludedPathNull() {
- ReferrerFilter rf = new ReferrerFilter(createConfiguration(false,
null, null, null, null));
-
+ ReferrerFilter rf = new ReferrerFilter(createConfiguration(false,
null, null, null, null),
+ Collections.emptyList());
+
assertFalse(rf.isValidRequest(getRequest(null, null, "/test_path")));
assertFalse(rf.isValidRequest(getRequest(null, null,
"/test_path/subtree")));
assertFalse(rf.isValidRequest(getRequest(null, null,
"/test_path_sibling")));
@@ -168,10 +172,59 @@ public class ReferrerFilterTest {
assertTrue(rf.isValidRequest(getRequest("relative", null,
"/test_path")));
assertFalse(rf.isValidRequest(getRequest("http://yet.another.abshost:80", null,
"/test_path")));
}
-
+
+ @Test
+ public void testWithAmendments() {
+ ReferrerFilterAmendment amendment = new
ReferrerFilterAmendmentImpl(new Config() {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ throw new UnsupportedOperationException("Unimplemented method
'annotationType'");
+ }
+
+ @Override
+ public String[] allow_hosts() {
+ return new String[]{"test.com"};
+ }
+
+ @Override
+ public String[] allow_hosts_regexp() {
+ return new String[]{".*test2.com.*"};
+ }
+
+ @Override
+ public String[] exclude_agents_regexp() {
+ return null;
+ }
+
+ @Override
+ public String[] exclude_paths() {
+ return new String[]{"/testpath2"};
+ }
+
+ });
+ ReferrerFilter rf = new ReferrerFilter(createConfiguration(false,
null, new String[]{".*test1.com.*"}, null, null),
Collections.singletonList(amendment));
+
+ assertTrue(rf.isValidRequest(getRequest(null, null, "/testpath2")));
+ assertFalse(rf.isValidRequest(getRequest(null, null, "/test1path")));
+
+ assertFalse(rf.isValidRequest(getRequest("http://testnotvalid.com:80",
null, "/test_path")));
+ assertTrue(rf.isValidRequest(getRequest("http://test1.com:80", null,
"/test_path")));
+ assertTrue(rf.isValidRequest(getRequest("http://test2.com:80", null,
"/test_path")));
+ }
+
+
+ @Test
+ public void testAllowsWithOrigin(){
+ HttpServletRequest request = getRequest(null);
+ when(request.getHeader("origin")).thenReturn("http://abshost");
+ Assert.assertEquals(true, filter.isValidRequest(request));
+ }
+
@Test
public void testAllowEmpty() {
- ReferrerFilter rf = new ReferrerFilter(createConfiguration(true, null,
null, null, null));
+ ReferrerFilter rf = new ReferrerFilter(createConfiguration(true, null,
null, null, null),
+ Collections.emptyList());
assertTrue(rf.isValidRequest(getRequest(null, null, "/test_path")));
assertTrue(rf.isValidRequest(getRequest("", null, null)));