diff --git a/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java b/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java index dbe4e7be654..56d3f553a60 100644 --- a/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java +++ b/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java @@ -18,7 +18,11 @@ import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -31,6 +35,7 @@ * strategies for IP addresses. * * @author Rob Winch + * @author Andrey Litvitski * @since 7.1 */ public final class InetAddressMatchers { @@ -86,15 +91,22 @@ public static InetAddressMatcher fromIpAddress(String ipAddress) { * @author Gábor Vaspöri * @author Rossen Stoyanchev * @author Rob Winch + * @author Andrey Litvitski */ public static final class Builder { - private final List matchers = new ArrayList<>(); + private final Set includeAddresses = new LinkedHashSet<>(); + + private final Set excludeAddresses = new LinkedHashSet<>(); + + private final List customMatchers = new ArrayList<>(); private boolean reportOnly; /** - * Adds an include list matcher that permits only the specified addresses. + * Adds IP address patterns to the include list that permits only the specified + * addresses. If called multiple times, the addresses are combined into a single + * include rule. * @param addresses the list of IP address patterns to include (cannot be null or * empty) * @return this builder for method chaining @@ -102,15 +114,13 @@ public static final class Builder { */ public Builder includeAddresses(List addresses) { Assert.notEmpty(addresses, "addresses cannot be empty"); - List matchers = addresses.stream() - .map(IpInetAddressMatcher::new) - .toList(); - this.matchers.add(new IncludeListInetAddressMatcher(matchers)); + this.includeAddresses.addAll(addresses); return this; } /** - * Adds an exclude list matcher that blocks the specified addresses. + * Adds IP address patterns to the exclude list 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 @@ -118,10 +128,7 @@ public Builder includeAddresses(List addresses) { */ public Builder excludeAddresses(List addresses) { Assert.notEmpty(addresses, "addresses cannot be empty"); - List matchers = addresses.stream() - .map(IpInetAddressMatcher::new) - .toList(); - this.matchers.add(new ExcludeListInetAddressMatcher(matchers)); + this.excludeAddresses.addAll(addresses); return this; } @@ -135,9 +142,7 @@ public Builder excludeAddresses(List addresses) { */ public Builder matchAll(InetAddressMatcher... matchers) { Assert.notEmpty(matchers, "matchers cannot be empty"); - for (InetAddressMatcher matcher : matchers) { - this.matchers.add(matcher); - } + Collections.addAll(this.customMatchers, matchers); return this; } @@ -157,7 +162,23 @@ public Builder reportOnly() { * @return the constructed {@link InetAddressMatcher} */ public InetAddressMatcher build() { - return new CompositeInetAddressMatcher(this.matchers, this.reportOnly); + List result = new ArrayList<>(); + if (!this.includeAddresses.isEmpty()) { + result.add(createListMatcher(this.includeAddresses, IncludeListInetAddressMatcher::new)); + } + if (!this.excludeAddresses.isEmpty()) { + result.add(createListMatcher(this.excludeAddresses, ExcludeListInetAddressMatcher::new)); + } + result.addAll(this.customMatchers); + return new CompositeInetAddressMatcher(result, this.reportOnly); + } + + private InetAddressMatcher createListMatcher(Set addresses, + Function, InetAddressMatcher> constructor) { + List matchers = addresses.stream() + .map(IpInetAddressMatcher::new) + .toList(); + return constructor.apply(matchers); } } diff --git a/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java b/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java index 387af3c3814..eadf69d46bd 100644 --- a/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java +++ b/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java @@ -31,6 +31,7 @@ * Tests for {@link InetAddressMatchers}. * * @author Rob Winch + * @author Andrey Litvitski */ class InetAddressMatchersTests { @@ -200,6 +201,17 @@ void buildWhenMultipleMatchersThenAppliesAndLogic(String testAddress) throws Exc assertThat(matcher.matches(address)).isEqualTo(expected); } + @Test + void includeAddressesWhenCalledMultipleTimesThenMatchesAllAddresses() throws Exception { + InetAddressMatcher matcher = InetAddressMatchers.builder() + .includeAddresses(List.of("192.168.1.1")) + .includeAddresses(List.of("10.0.0.1")) + .build(); + assertThat(matcher.matches(InetAddress.getByName("192.168.1.1"))).isTrue(); + assertThat(matcher.matches(InetAddress.getByName("10.0.0.1"))).isTrue(); + assertThat(matcher.matches(InetAddress.getByName("8.8.8.8"))).isFalse(); + } + } @Nested