From 6a4d98a8653d21acec1ea517229c6d73ac6da359 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 19 Apr 2020 15:55:22 -0700 Subject: [PATCH] Allow exposure patterns to match dashed IDs Update `ExposeExcludePropertyEndpointFilter` so that patterns will also match endpoint IDs that contain a dash. Closes gh-20997 --- .../ExposeExcludePropertyEndpointFilter.java | 96 ++++++++++++------- ...oseExcludePropertyEndpointFilterTests.java | 8 +- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java index e9786a23239..4e53546f32f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -20,10 +20,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; @@ -35,7 +34,7 @@ import org.springframework.util.Assert; /** * {@link EndpointFilter} that will filter endpoints based on {@code include} and - * {@code exclude} properties. + * {@code exclude} patterns. * * @param the endpoint type * @author Phillip Webb @@ -45,11 +44,11 @@ public class ExposeExcludePropertyEndpointFilter> private final Class endpointType; - private final Set include; + private final EndpointPatterns include; - private final Set exclude; + private final EndpointPatterns exclude; - private final Set exposeDefaults; + private final EndpointPatterns exposeDefaults; public ExposeExcludePropertyEndpointFilter(Class endpointType, Environment environment, String prefix, String... exposeDefaults) { @@ -58,37 +57,22 @@ public class ExposeExcludePropertyEndpointFilter> Assert.hasText(prefix, "Prefix must not be empty"); Binder binder = Binder.get(environment); this.endpointType = endpointType; - this.include = bind(binder, prefix + ".include"); - this.exclude = bind(binder, prefix + ".exclude"); - this.exposeDefaults = asSet(Arrays.asList(exposeDefaults)); + this.include = new EndpointPatterns(bind(binder, prefix + ".include")); + this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude")); + this.exposeDefaults = new EndpointPatterns(exposeDefaults); } public ExposeExcludePropertyEndpointFilter(Class endpointType, Collection include, Collection exclude, String... exposeDefaults) { Assert.notNull(endpointType, "EndpointType Type must not be null"); this.endpointType = endpointType; - this.include = asSet(include); - this.exclude = asSet(exclude); - this.exposeDefaults = asSet(Arrays.asList(exposeDefaults)); + this.include = new EndpointPatterns(include); + this.exclude = new EndpointPatterns(exclude); + this.exposeDefaults = new EndpointPatterns(exposeDefaults); } - private Set bind(Binder binder, String name) { - return asSet(binder.bind(name, Bindable.listOf(String.class)).map(this::cleanup).orElseGet(ArrayList::new)); - } - - private List cleanup(List values) { - return values.stream().map(this::cleanup).collect(Collectors.toList()); - } - - private String cleanup(String value) { - return "*".equals(value) ? "*" : EndpointId.fromPropertyValue(value).toLowerCaseString(); - } - - private Set asSet(Collection items) { - if (items == null) { - return Collections.emptySet(); - } - return items.stream().map((item) -> item.toLowerCase(Locale.ENGLISH)).collect(Collectors.toSet()); + private List bind(Binder binder, String name) { + return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new); } @Override @@ -101,20 +85,62 @@ public class ExposeExcludePropertyEndpointFilter> private boolean isExposed(ExposableEndpoint endpoint) { if (this.include.isEmpty()) { - return this.exposeDefaults.contains("*") || contains(this.exposeDefaults, endpoint); + return this.exposeDefaults.matchesAll() || this.exposeDefaults.matches(endpoint); } - return this.include.contains("*") || contains(this.include, endpoint); + return this.include.matchesAll() || this.include.matches(endpoint); } private boolean isExcluded(ExposableEndpoint endpoint) { if (this.exclude.isEmpty()) { return false; } - return this.exclude.contains("*") || contains(this.exclude, endpoint); + return this.exclude.matchesAll() || this.exclude.matches(endpoint); } - private boolean contains(Set items, ExposableEndpoint endpoint) { - return items.contains(endpoint.getEndpointId().toLowerCaseString()); + /** + * A set of endpoint patterns used to match IDs. + */ + private static class EndpointPatterns { + + private final boolean empty; + + private final boolean matchesAll; + + private final Set endpointIds; + + EndpointPatterns(String[] patterns) { + this((patterns != null) ? Arrays.asList(patterns) : (Collection) null); + } + + EndpointPatterns(Collection patterns) { + patterns = (patterns != null) ? patterns : Collections.emptySet(); + boolean matchesAll = false; + Set endpointIds = new LinkedHashSet<>(); + for (String pattern : patterns) { + if ("*".equals(pattern)) { + matchesAll = true; + } + else { + endpointIds.add(EndpointId.fromPropertyValue(pattern)); + } + } + this.empty = patterns.isEmpty(); + this.matchesAll = matchesAll; + this.endpointIds = endpointIds; + } + + boolean isEmpty() { + return this.empty; + } + + boolean matchesAll() { + return this.matchesAll; + } + + boolean matches(ExposableEndpoint endpoint) { + return this.endpointIds.contains(endpoint.getEndpointId()); + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java index a852ffdf01d..57b7fcb5deb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -147,6 +147,12 @@ public class ExposeExcludePropertyEndpointFilterTests { assertThat(match(EndpointId.of("fooBar"))).isTrue(); } + @Test // gh-20997 + public void matchWhenDashInName() throws Exception { + setupFilter("bus-refresh", ""); + assertThat(match(EndpointId.of("bus-refresh"))).isTrue(); + } + private void setupFilter(String include, String exclude) { MockEnvironment environment = new MockEnvironment(); environment.setProperty("foo.include", include);