Browse Source
Co-authored-by: Gábor Vaspöri <gabor.vaspori@gmail.com> Co-authored-by: Kian Jamali <kianjamali123@gmail.com> Co-authored-by: Rossen Stoyanchev <rstoyanchev@users.noreply.github.com>pull/18593/head
11 changed files with 1352 additions and 90 deletions
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
[[new]] |
||||
= What's New in Spring Security 7.1 |
||||
|
||||
This is a placeholder for updates to Spring Security 7.1 |
||||
* https://github.com/spring-projects/spring-security/pull/18634[gh-18634] - Added javadoc:org.springframework.security.web.util.matcher.InetAddressMatcher[] |
||||
|
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
/** |
||||
* Matches an {@link InetAddress}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
* @since 7.1 |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface InetAddressMatcher { |
||||
|
||||
/** |
||||
* Whether the given address matches. |
||||
* @param address the {@link InetAddress} to check (may be {@code null}) |
||||
* @return {@code true} if the address matches, {@code false} otherwise |
||||
*/ |
||||
boolean matches(@Nullable InetAddress address); |
||||
|
||||
/** |
||||
* Whether the given address string matches. |
||||
* @param address the IP address string to check (may be {@code null}) |
||||
* @return {@code true} if the address matches, {@code false} otherwise |
||||
*/ |
||||
default boolean matches(@Nullable String address) { |
||||
return (address != null) ? matches(InetAddressParser.parseAddress(address)) : false; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,379 @@
@@ -0,0 +1,379 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Factory for creating {@link InetAddressMatcher} instances with various matching |
||||
* strategies for IP addresses. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.1 |
||||
*/ |
||||
public final class InetAddressMatchers { |
||||
|
||||
private InetAddressMatchers() { |
||||
} |
||||
|
||||
/** |
||||
* Creates a new builder for configuring an {@link InetAddressMatcher}. |
||||
* @return a new {@link Builder} instance |
||||
*/ |
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new builder configured to match external (non-private) IP addresses. |
||||
* @return a {@link Builder} configured to match external addresses |
||||
*/ |
||||
public static Builder matchExternal() { |
||||
return builder().matchAll(ExternalInetAddressMatcher.getInstance()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new builder configured to match internal (private) IP addresses. |
||||
* <p> |
||||
* Internal addresses include loopback addresses (127.0.0.0/8 for IPv4, ::1 for IPv6), |
||||
* private IPv4 address ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and IPv6 |
||||
* Unique Local Addresses (fc00::/7). |
||||
* @return a {@link Builder} configured to match internal addresses |
||||
*/ |
||||
public static Builder matchInternal() { |
||||
return builder().matchAll(InternalInetAddressMatcher.getInstance()); |
||||
} |
||||
|
||||
/** |
||||
* A builder for constructing {@link InetAddressMatcher} instances with various |
||||
* matching rules. |
||||
* |
||||
* @author Kian Jamali |
||||
* @author Gábor Vaspöri |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
public static final class Builder { |
||||
|
||||
private final List<InetAddressMatcher> matchers = new ArrayList<>(); |
||||
|
||||
private boolean reportOnly; |
||||
|
||||
/** |
||||
* Adds an include list matcher that permits only the specified addresses. |
||||
* @param addresses the list of IP address patterns to include (cannot be null or |
||||
* empty) |
||||
* @return this builder for method chaining |
||||
* @throws IllegalArgumentException if addresses is null or empty |
||||
*/ |
||||
public Builder includeAddresses(List<String> addresses) { |
||||
Assert.notEmpty(addresses, "addresses cannot be empty"); |
||||
List<InetAddressMatcher> matchers = addresses.stream() |
||||
.<InetAddressMatcher>map(IpInetAddressMatcher::new) |
||||
.toList(); |
||||
this.matchers.add(new IncludeListInetAddressMatcher(matchers)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Adds an exclude list matcher that blocks the specified addresses. |
||||
* @param addresses the list of IP address patterns to exclude (cannot be null or |
||||
* empty) |
||||
* @return this builder for method chaining |
||||
* @throws IllegalArgumentException if addresses is null or empty |
||||
*/ |
||||
public Builder excludeAddresses(List<String> addresses) { |
||||
Assert.notEmpty(addresses, "addresses cannot be empty"); |
||||
List<InetAddressMatcher> matchers = addresses.stream() |
||||
.<InetAddressMatcher>map(IpInetAddressMatcher::new) |
||||
.toList(); |
||||
this.matchers.add(new ExcludeListInetAddressMatcher(matchers)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Adds custom matchers to the matcher chain. All matchers must match for an |
||||
* address to be permitted. |
||||
* @param matchers the custom {@link InetAddressMatcher} instances to add (cannot |
||||
* be null or empty) |
||||
* @return this builder for method chaining |
||||
* @throws IllegalArgumentException if matchers is null or empty |
||||
*/ |
||||
public Builder matchAll(InetAddressMatcher... matchers) { |
||||
Assert.notEmpty(matchers, "matchers cannot be empty"); |
||||
for (InetAddressMatcher matcher : matchers) { |
||||
this.matchers.add(matcher); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Configures the matcher to operate in report-only mode. In this mode, matching |
||||
* logic is evaluated and logged, but all addresses are allowed regardless of |
||||
* match results. |
||||
* @return this builder for method chaining |
||||
*/ |
||||
public Builder reportOnly() { |
||||
this.reportOnly = true; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Builds the configured {@link InetAddressMatcher}. |
||||
* @return the constructed {@link InetAddressMatcher} |
||||
*/ |
||||
public InetAddressMatcher build() { |
||||
return new CompositeInetAddressMatcher(this.matchers, this.reportOnly); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* An {@link InetAddressMatcher} that matches addresses against an include list. Only |
||||
* addresses that match an entry in the include list are permitted. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
static final class IncludeListInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private final List<InetAddressMatcher> includeList; |
||||
|
||||
IncludeListInetAddressMatcher(List<InetAddressMatcher> includeList) { |
||||
Assert.notEmpty(includeList, "includeList cannot be null or empty"); |
||||
this.includeList = new ArrayList<>(includeList); |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress address) { |
||||
for (InetAddressMatcher matcher : this.includeList) { |
||||
if (matcher.matches(address)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "IncludeListInetAddressMatcher[\"" + this.includeList + "\"]"; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* An {@link InetAddressMatcher} that matches addresses against an exclude list. |
||||
* Addresses that match an entry in the exclude list are rejected. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
static final class ExcludeListInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private final List<InetAddressMatcher> excludeList; |
||||
|
||||
ExcludeListInetAddressMatcher(List<InetAddressMatcher> excludeList) { |
||||
Assert.notEmpty(excludeList, "excludeList cannot be null or empty"); |
||||
this.excludeList = new ArrayList<>(excludeList); |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress address) { |
||||
for (InetAddressMatcher matcher : this.excludeList) { |
||||
if (matcher.matches(address)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "ExcludeListInetAddressMatcher[\"" + this.excludeList + "\"]"; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* An {@link InetAddressMatcher} that matches internal (private) addresses. |
||||
* <p> |
||||
* Internal addresses include loopback addresses (127.0.0.0/8 for IPv4, ::1 for IPv6), |
||||
* private IPv4 address ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and IPv6 |
||||
* Unique Local Addresses (fc00::/7). |
||||
* |
||||
* @author Gábor Vaspöri |
||||
* @author Kian Jamali |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
static final class InternalInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private static final InternalInetAddressMatcher INSTANCE = new InternalInetAddressMatcher(); |
||||
|
||||
static InternalInetAddressMatcher getInstance() { |
||||
return INSTANCE; |
||||
} |
||||
|
||||
private InternalInetAddressMatcher() { |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress address) { |
||||
if (address == null) { |
||||
return false; |
||||
} |
||||
if (address.isLoopbackAddress() || address.isLinkLocalAddress() || address.isSiteLocalAddress()) { |
||||
return true; |
||||
} |
||||
|
||||
byte[] rawAddress = address.getAddress(); |
||||
|
||||
if (rawAddress.length == 16) { |
||||
// Convert signed bytes to unsigned ints for easier matching logic
|
||||
int[] iAddr = new int[rawAddress.length]; |
||||
for (int i = 0; i < rawAddress.length; i++) { |
||||
iAddr[i] = Byte.toUnsignedInt(rawAddress[i]); |
||||
} |
||||
|
||||
/* |
||||
* IPv6, check for Unique Local Addresses. We cannot rely on |
||||
* Inet6Address.isSiteLocalAddress() here because the JVM implementation |
||||
* dictates that fec0::/10 is the only site-local IPv6 address space, |
||||
* based on the outdated RFC 2373. That RFC was deprecated by the IETF in |
||||
* 2004 in favor of fc00::/7 (RFC 4193). To keep our private network |
||||
* checking accurate to modern subnets, we maintain manual parsing. |
||||
*/ |
||||
if (iAddr[0] == 0xfc || iAddr[0] == 0xfd) { |
||||
return true; |
||||
} |
||||
|
||||
// IPv4/IPv6 translation, 64:ff9b
|
||||
if (iAddr[0] == 0x00 && iAddr[1] == 0x64 && iAddr[2] == 0xff && iAddr[3] == 0x9b) { |
||||
try { |
||||
InetAddress ipv4Part = InetAddress.getByAddress( |
||||
new byte[] { rawAddress[12], rawAddress[13], rawAddress[14], rawAddress[15] }); |
||||
|
||||
if (ipv4Part.isLoopbackAddress() || ipv4Part.isLinkLocalAddress() |
||||
|| ipv4Part.isSiteLocalAddress()) { |
||||
return true; |
||||
} |
||||
} |
||||
catch (java.net.UnknownHostException ex) { |
||||
// Should not happen for 4-byte array
|
||||
} |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "InternalInetAddressMatcher"; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* An {@link InetAddressMatcher} that matches external (public) addresses. |
||||
* <p> |
||||
* External addresses are any addresses that are not internal (private) addresses. |
||||
* This matcher delegates to {@link InternalInetAddressMatcher} and negates the |
||||
* result. |
||||
* |
||||
* @author Gábor Vaspöri |
||||
* @author Kian Jamali |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
static final class ExternalInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private static final ExternalInetAddressMatcher INSTANCE = new ExternalInetAddressMatcher(); |
||||
|
||||
static ExternalInetAddressMatcher getInstance() { |
||||
return INSTANCE; |
||||
} |
||||
|
||||
private final InternalInetAddressMatcher internalMatcher = InternalInetAddressMatcher.getInstance(); |
||||
|
||||
private ExternalInetAddressMatcher() { |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress address) { |
||||
return !this.internalMatcher.matches(address); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "ExternalInetAddressMatcher"; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A composite {@link InetAddressMatcher} that chains multiple matchers together. All |
||||
* matchers must match for an address to be allowed. If report-only mode is enabled, |
||||
* matching results are logged but all addresses are permitted. |
||||
* |
||||
* @author Gábor Vaspöri |
||||
* @author Kian Jamali |
||||
* @author Rossen Stoyanchev |
||||
* @author Rob Winch |
||||
*/ |
||||
static final class CompositeInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private static final Log logger = LogFactory.getLog(InetAddressMatcher.class); |
||||
|
||||
private final List<InetAddressMatcher> matchers; |
||||
|
||||
private final boolean reportOnly; |
||||
|
||||
CompositeInetAddressMatcher(List<InetAddressMatcher> matchers, boolean reportOnly) { |
||||
this.matchers = new ArrayList<>(matchers); |
||||
this.reportOnly = reportOnly; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress address) { |
||||
boolean result = doMatch(address); |
||||
return (this.reportOnly || result); |
||||
} |
||||
|
||||
private boolean doMatch(@Nullable InetAddress address) { |
||||
for (InetAddressMatcher matcher : this.matchers) { |
||||
if (!matcher.matches(address)) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("InetAddress " + address + " blocked by " + matcher); |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Utility class for parsing IP addresses. |
||||
* |
||||
* @author Luke Taylor |
||||
* @author Steve Riesenberg |
||||
* @author Andrey Litvitski |
||||
* @author Rob Winch |
||||
* @since 7.1 |
||||
*/ |
||||
final class InetAddressParser { |
||||
|
||||
private static Pattern IPV4 = Pattern.compile("^\\d{1,3}(?:\\.\\d{1,3}){0,3}(?:/\\d{1,2})?$"); |
||||
|
||||
/** |
||||
* Parses the given address string into an {@link InetAddress}. |
||||
* @param address the IP address string to parse |
||||
* @return the parsed {@link InetAddress} |
||||
* @throws IllegalArgumentException if the address cannot be parsed or appears to be a |
||||
* hostname |
||||
*/ |
||||
static InetAddress parseAddress(String address) { |
||||
assertNotHostName(address); |
||||
try { |
||||
return InetAddress.getByName(address); |
||||
} |
||||
catch (UnknownHostException ex) { |
||||
throw new IllegalArgumentException("Failed to parse address '" + address + "'", ex); |
||||
} |
||||
} |
||||
|
||||
static void assertNotHostName(String ipAddress) { |
||||
Assert.isTrue(isIpAddress(ipAddress), |
||||
() -> String.format("ipAddress %s doesn't look like an IP Address. Is it a host name?", ipAddress)); |
||||
} |
||||
|
||||
private static boolean isIpAddress(String ipAddress) { |
||||
if (!org.springframework.util.StringUtils.hasText(ipAddress)) { |
||||
return false; |
||||
} |
||||
// @formatter:off
|
||||
return IPV4.matcher(ipAddress).matches() |
||||
|| ipAddress.charAt(0) == '[' |
||||
|| ipAddress.charAt(0) == ':' |
||||
|| Character.digit(ipAddress.charAt(0), 16) != -1 |
||||
&& ipAddress.indexOf(':') > 0; |
||||
// @formatter:on
|
||||
} |
||||
|
||||
private InetAddressParser() { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.util.Objects; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Implementation of {@link InetAddressMatcher} that matches IP addresses with support for |
||||
* CIDR notation (e.g., 192.168.1.0/24). |
||||
* <p> |
||||
* Both IPv4 and IPv6 addresses are supported. The matcher can be configured with either a |
||||
* specific IP address or a subnet using CIDR notation. |
||||
* <p> |
||||
* The logic from this class was migrated from {@link IpAddressMatcher} to provide a more |
||||
* general API that did not depend on the servlet APIs (e.g. HttpServletRequest). |
||||
* |
||||
* @author Luke Taylor |
||||
* @author Steve Riesenberg |
||||
* @author Andrey Litvitski |
||||
* @since 7.1 |
||||
* @see IpAddressMatcher |
||||
*/ |
||||
final class IpInetAddressMatcher implements InetAddressMatcher { |
||||
|
||||
private static final Log logger = LogFactory.getLog(IpAddressMatcher.class); |
||||
|
||||
private final InetAddress requiredAddress; |
||||
|
||||
private final int nMaskBits; |
||||
|
||||
IpInetAddressMatcher(String ipAddress) { |
||||
Assert.hasText(ipAddress, "ipAddress cannot be empty"); |
||||
String requiredAddress; |
||||
int nMaskBits; |
||||
if (ipAddress.indexOf('/') > 0) { |
||||
String[] parts = Objects.requireNonNull(StringUtils.split(ipAddress, "/")); |
||||
requiredAddress = parts[0]; |
||||
nMaskBits = Integer.parseInt(parts[1]); |
||||
} |
||||
else { |
||||
requiredAddress = ipAddress; |
||||
nMaskBits = -1; |
||||
} |
||||
this.requiredAddress = InetAddressParser.parseAddress(requiredAddress); |
||||
this.nMaskBits = nMaskBits; |
||||
Assert.isTrue(this.requiredAddress.getAddress().length * 8 >= this.nMaskBits, () -> String |
||||
.format("IP address %s is too short for bitmask of length %d", requiredAddress, this.nMaskBits)); |
||||
} |
||||
|
||||
private static InetAddress parse(String address) { |
||||
try { |
||||
InetAddress result = InetAddress.getByName(address); |
||||
if (address.matches(".*[a-zA-Z\\-].*$") && !address.contains(":")) { |
||||
logger.warn("Hostname '" + address + "' resolved to " + result.toString() |
||||
+ " will be used on IP address matching"); |
||||
} |
||||
return result; |
||||
} |
||||
catch (UnknownHostException ex) { |
||||
throw new IllegalArgumentException(String.format("Failed to parse address '%s'", address), ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(@Nullable InetAddress toCheck) { |
||||
if (toCheck == null) { |
||||
return false; |
||||
} |
||||
if (this.nMaskBits < 0) { |
||||
return toCheck.equals(this.requiredAddress); |
||||
} |
||||
byte[] remAddr = toCheck.getAddress(); |
||||
byte[] reqAddr = this.requiredAddress.getAddress(); |
||||
int nMaskFullBytes = this.nMaskBits / 8; |
||||
byte finalByte = (byte) (0xFF00 >> (this.nMaskBits & 0x07)); |
||||
for (int i = 0; i < nMaskFullBytes; i++) { |
||||
if (remAddr[i] != reqAddr[i]) { |
||||
return false; |
||||
} |
||||
} |
||||
if (finalByte != 0) { |
||||
return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
String hostAddress = this.requiredAddress.getHostAddress(); |
||||
return (this.nMaskBits < 0) ? "IpAddress [" + hostAddress + "]" |
||||
: "IpAddress [" + hostAddress + "/" + this.nMaskBits + "]"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link InetAddressMatcher}. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
class InetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void matchesWhenStringValidIpv4ThenReturnsTrue() { |
||||
InetAddressMatcher matcher = (address) -> address.getHostAddress().equals("192.168.1.1"); |
||||
assertThat(matcher.matches("192.168.1.1")).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringValidIpv6ThenReturnsTrue() { |
||||
InetAddressMatcher matcher = (address) -> address.getHostAddress().equals("fe80:0:0:0:21f:5bff:fe33:bd68"); |
||||
assertThat(matcher.matches("fe80::21f:5bff:fe33:bd68")).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringNullThenReturnsFalse() { |
||||
InetAddressMatcher matcher = (address) -> true; |
||||
assertThat(matcher.matches((String) null)).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringInvalidThenThrowsIllegalArgumentException() { |
||||
InetAddressMatcher matcher = (address) -> true; |
||||
assertThat(matcher.matches("192.168.1.1")).isTrue(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> matcher.matches("not.an.ip.address")); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringMatchesPredicateThenReturnsTrue() { |
||||
InetAddressMatcher matcher = (address) -> address.getHostAddress().startsWith("192.168"); |
||||
assertThat(matcher.matches("192.168.1.1")).isTrue(); |
||||
assertThat(matcher.matches("192.168.100.200")).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringDoesNotMatchPredicateThenReturnsFalse() { |
||||
InetAddressMatcher matcher = (address) -> address.getHostAddress().startsWith("192.168"); |
||||
assertThat(matcher.matches("10.0.0.1")).isFalse(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,499 @@
@@ -0,0 +1,499 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.ValueSource; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link InetAddressMatchers}. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
class InetAddressMatchersTests { |
||||
|
||||
@Test |
||||
void builderWhenInvokedThenReturnsBuilder() { |
||||
assertThat(InetAddressMatchers.builder()).isNotNull(); |
||||
} |
||||
|
||||
@Test |
||||
void matchExternalWhenInvokedThenReturnsBuilder() { |
||||
InetAddressMatchers.Builder builder = InetAddressMatchers.matchExternal(); |
||||
assertThat(builder).isNotNull(); |
||||
} |
||||
|
||||
@Test |
||||
void matchInternalWhenInvokedThenReturnsBuilder() { |
||||
InetAddressMatchers.Builder builder = InetAddressMatchers.matchInternal(); |
||||
assertThat(builder).isNotNull(); |
||||
} |
||||
|
||||
@Nested |
||||
class BuilderTests { |
||||
|
||||
@Test |
||||
void includeAddressesWhenNullThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> InetAddressMatchers.builder().includeAddresses(null)) |
||||
.withMessage("addresses cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void includeAddressesWhenEmptyListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> InetAddressMatchers.builder().includeAddresses(List.of())) |
||||
.withMessage("addresses cannot be empty"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "192.168.1.2" }) |
||||
void includeAddressesWhenSingleAddressThenMatchesOnlyThatAddress(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().includeAddresses(List.of("192.168.1.1")).build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.equals("192.168.1.1"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "10.0.0.1", "8.8.8.8" }) |
||||
void includeAddressesWhenMultipleAddressesThenMatchesAny(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.includeAddresses(List.of("192.168.1.1", "10.0.0.1")) |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.equals("192.168.1.1") || testAddress.equals("10.0.0.1"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "192.168.1.255", "192.168.2.1" }) |
||||
void includeAddressesWhenCidrNotationThenMatchesSubnet(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.includeAddresses(List.of("192.168.1.0/24")) |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.startsWith("192.168.1."); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@Test |
||||
void excludeAddressesWhenNullThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> InetAddressMatchers.builder().excludeAddresses(null)) |
||||
.withMessage("addresses cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void excludeAddressesWhenEmptyListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> InetAddressMatchers.builder().excludeAddresses(List.of())) |
||||
.withMessage("addresses cannot be empty"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "192.168.1.2" }) |
||||
void excludeAddressesWhenSingleAddressThenBlocksOnlyThatAddress(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().excludeAddresses(List.of("192.168.1.1")).build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = !testAddress.equals("192.168.1.1"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "10.0.0.1", "8.8.8.8" }) |
||||
void excludeAddressesWhenMultipleAddressesThenBlocksAll(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.excludeAddresses(List.of("192.168.1.1", "10.0.0.1")) |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = !testAddress.equals("192.168.1.1") && !testAddress.equals("10.0.0.1"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "192.168.1.255", "192.168.2.1" }) |
||||
void excludeAddressesWhenCidrNotationThenBlocksSubnet(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.excludeAddresses(List.of("192.168.1.0/24")) |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = !testAddress.startsWith("192.168.1."); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "10.0.0.1", "192.168.1.1" }) |
||||
void matchAllWhenVarargsThenAddsMatchersToChain(String testAddress) throws Exception { |
||||
InetAddressMatcher customMatcher = (address) -> address.getHostAddress().startsWith("10."); |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().matchAll(customMatcher).build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.startsWith("10."); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@Test |
||||
void matchAllWhenNullVarargsThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> InetAddressMatchers.builder().matchAll((InetAddressMatcher[]) null)) |
||||
.withMessage("matchers cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void matchAllWhenEmptyVarargsThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> InetAddressMatchers.builder().matchAll(new InetAddressMatcher[0])) |
||||
.withMessage("matchers cannot be empty"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "10.0.0.1", "10.0.0.2", "192.168.1.1" }) |
||||
void matchAllWhenMultipleMatchersThenAppliesAndLogic(String testAddress) throws Exception { |
||||
InetAddressMatcher startsWithTen = (address) -> address.getHostAddress().startsWith("10."); |
||||
InetAddressMatcher endsWithOne = (address) -> address.getHostAddress().endsWith(".1"); |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().matchAll(startsWithTen, endsWithOne).build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.startsWith("10.") && testAddress.endsWith(".1"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "8.8.8.8" }) |
||||
void reportOnlyWhenSetThenAllowsAllAddresses(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.excludeAddresses(List.of("192.168.1.1")) |
||||
.reportOnly() |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
assertThat(matcher.matches(address)).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "192.168.1.100", "192.168.2.1" }) |
||||
void buildWhenMultipleMatchersThenAppliesAndLogic(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.includeAddresses(List.of("192.168.1.0/24")) |
||||
.excludeAddresses(List.of("192.168.1.100")) |
||||
.build(); |
||||
InetAddress address = InetAddress.getByName(testAddress); |
||||
boolean expected = testAddress.startsWith("192.168.1.") && !testAddress.equals("192.168.1.100"); |
||||
assertThat(matcher.matches(address)).isEqualTo(expected); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class IncludeListInetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void constructorWhenNullListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new InetAddressMatchers.IncludeListInetAddressMatcher(null)) |
||||
.withMessage("includeList cannot be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenEmptyListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new InetAddressMatchers.IncludeListInetAddressMatcher(List.of())) |
||||
.withMessage("includeList cannot be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenAddressInListThenReturnsTrue() throws Exception { |
||||
String addressString = "192.168.1.1"; |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().includeAddresses(List.of(addressString)).build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(addressString))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenAddressNotInListThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().includeAddresses(List.of("192.168.1.1")).build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.2"))).isFalse(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class ExcludeListInetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void constructorWhenNullListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new InetAddressMatchers.ExcludeListInetAddressMatcher(null)) |
||||
.withMessage("excludeList cannot be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenEmptyListThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> new InetAddressMatchers.ExcludeListInetAddressMatcher(List.of())) |
||||
.withMessage("excludeList cannot be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenAddressInListThenReturnsFalse() throws Exception { |
||||
String addressString = "192.168.1.1"; |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().excludeAddresses(List.of(addressString)).build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(addressString))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenAddressNotInListThenReturnsTrue() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder().excludeAddresses(List.of("192.168.1.1")).build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.2"))).isTrue(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class InternalInetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void matchesWhenInetAddressNullThenReturnsFalse() { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches((InetAddress) null)).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "127.0.0.1", "127.0.0.255" }) |
||||
void matchesWhenIpv4LoopbackThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6LoopbackThenReturnsTrue() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("::1"))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "10.0.0.1", "10.255.255.255" }) |
||||
void matchesWhenIpv4PrivateClass10ThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.0.1", "192.168.255.255" }) |
||||
void matchesWhenIpv4PrivateClass192ThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "169.254.0.0", "169.254.169.254", "169.254.255.255" }) |
||||
void matchesWhenIpv4LinkLocalThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "172.16.0.1", "172.16.255.255", "172.17.1.1", "172.31.255.255" }) |
||||
void matchesWhenIpv4PrivateClass172ThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "::ffff:192.168.1.1", "::ffff:169.254.169.254", "::ffff:10.0.0.1" }) |
||||
void matchesWhenIpv4MappedIpv6InternalThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "fc00::1", "fd00::1" }) |
||||
void matchesWhenIpv6UniqueLocalThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource( |
||||
strings = { "64:ff9b::10.0.0.1", "64:ff9b::127.0.0.1", "64:ff9b::192.168.1.1", "64:ff9b::172.16.0.1" }) |
||||
void matchesWhenIpv6TranslationWithInternalIpv4ThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "64:ff9b::192.0.2.1", "64:ff9b::192.167.1.1" }) |
||||
void matchesWhenIpv6TranslationWithIpv4StartsWith192ButNot168ThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "64:ff9b::172.16.0.1", "64:ff9b::172.16.255.255" }) |
||||
void matchesWhenIpv6TranslationWithIpv4StartsWith172And16ThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "64:ff9b::8.8.8.8", "64:ff9b::1.1.1.1" }) |
||||
void matchesWhenIpv6TranslationWithExternalIpv4ThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6NonTranslationPrefixByte0ThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("65:ff9b::10.0.0.1"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6NonTranslationPrefixByte1ThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("64:fe9b::10.0.0.1"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6NonTranslationPrefixByte2ThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("64:ff9a::10.0.0.1"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6NonTranslationPrefixByte3ThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("64:ff9c::10.0.0.1"))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "8.8.8.8", "1.1.1.1" }) |
||||
void matchesWhenIpv4PublicThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.0.2.1", "192.167.1.1", "192.169.1.1" }) |
||||
void matchesWhenIpv4StartsWith192ButNot168ThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "172.15.1.1", "172.32.1.1" }) |
||||
void matchesWhenIpv4StartsWith172ButNotPrivate16To31ThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6PublicThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchInternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("2001:4860:4860::8888"))).isFalse(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class ExternalInetAddressMatcherTests { |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "8.8.8.8", "1.1.1.1" }) |
||||
void matchesWhenIpv4PublicThenReturnsTrue(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6PublicThenReturnsTrue() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("2001:4860:4860::8888"))).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "10.0.0.1", "172.16.0.1" }) |
||||
void matchesWhenIpv4PrivateThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4LoopbackThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("127.0.0.1"))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "169.254.0.0", "169.254.169.254", "169.254.255.255" }) |
||||
void matchesWhenIpv4LinkLocalThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6LoopbackThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("::1"))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "fc00::1", "fd00::1" }) |
||||
void matchesWhenIpv6UniqueLocalThenReturnsFalse(String address) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class CompositeInetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void matchesWhenAllMatchersTrueThenReturnsTrue() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.includeAddresses(List.of("192.168.1.0/24")) |
||||
.matchAll((address) -> address.getHostAddress().endsWith(".1")) |
||||
.build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.1"))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenOneMatcherFalseThenReturnsFalse() throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.includeAddresses(List.of("192.168.1.0/24")) |
||||
.matchAll((address) -> address.getHostAddress().endsWith(".1")) |
||||
.build(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.2"))).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "192.168.1.1", "8.8.8.8" }) |
||||
void matchesWhenReportOnlyThenAlwaysReturnsTrue(String testAddress) throws Exception { |
||||
InetAddressMatcher matcher = InetAddressMatchers.builder() |
||||
.excludeAddresses(List.of("192.168.1.1")) |
||||
.reportOnly() |
||||
.build(); |
||||
assertThat(matcher.matches(InetAddress.getByName(testAddress))).isTrue(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
/* |
||||
* Copyright 2004-present the original author or authors. |
||||
* |
||||
* Licensed 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 |
||||
* |
||||
* https://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.springframework.security.web.util.matcher; |
||||
|
||||
import java.net.InetAddress; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link IpInetAddressMatcher}. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
class IpInetAddressMatcherTests { |
||||
|
||||
@Test |
||||
void constructorWhenNullIpAddressThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new IpInetAddressMatcher(null)) |
||||
.withMessage("ipAddress cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenEmptyIpAddressThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new IpInetAddressMatcher("")) |
||||
.withMessage("ipAddress cannot be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenHostnameThenThrowsIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new IpInetAddressMatcher("example.com")) |
||||
.withMessageContaining("doesn't look like an IP Address"); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4ExactMatchThenReturnsTrue() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.1"))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4NoMatchThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.2"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6ExactMatchThenReturnsTrue() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("fe80::21f:5bff:fe33:bd68"); |
||||
assertThat(matcher.matches(InetAddress.getByName("fe80::21f:5bff:fe33:bd68"))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6NoMatchThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("fe80::21f:5bff:fe33:bd68"); |
||||
assertThat(matcher.matches(InetAddress.getByName("fe80::21f:5bff:fe33:bd69"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4WithCidrMatchesSubnetThenReturnsTrue() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.0/24"); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.1"))).isTrue(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.255"))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4WithCidrOutsideSubnetThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.0/24"); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.2.1"))).isFalse(); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.0.255"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6WithCidrMatchesSubnetThenReturnsTrue() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("2001:db8::/48"); |
||||
assertThat(matcher.matches(InetAddress.getByName("2001:db8:0:0:0:0:0:0"))).isTrue(); |
||||
assertThat(matcher.matches(InetAddress.getByName("2001:db8:0:ffff:ffff:ffff:ffff:ffff"))).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6WithCidrOutsideSubnetThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("2001:db8::/48"); |
||||
assertThat(matcher.matches(InetAddress.getByName("2001:db8:1:0:0:0:0:0"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv4AndIpv6AddressThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches(InetAddress.getByName("fe80::21f:5bff:fe33:bd68"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenIpv6AndIpv4AddressThenReturnsFalse() throws Exception { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("fe80::21f:5bff:fe33:bd68"); |
||||
assertThat(matcher.matches(InetAddress.getByName("192.168.1.1"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringIpv4MatchThenReturnsTrue() { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches("192.168.1.1")).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringIpv4NoMatchThenReturnsFalse() { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches("192.168.1.2")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenStringNullThenReturnsFalse() { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches((String) null)).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void matchesWhenInetAddressNullThenReturnsFalse() { |
||||
IpInetAddressMatcher matcher = new IpInetAddressMatcher("192.168.1.1"); |
||||
assertThat(matcher.matches((InetAddress) null)).isFalse(); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue