From df26e2460582102b1ea82b10722afbe79064643a Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 20 Apr 2020 12:03:25 -0700 Subject: [PATCH] Fix ConditionalOnAvailableEndpoint dashed matching Update `ConditionalOnAvailableEndpoint` so that it now uses the same matching code as the endpoint filter. This allows the condition to match endpoint IDs that contain a dash. In order to share logic, the `ExposeExcludePropertyEndpointFilter` class has been deprecated and its logic moved to a new `expose` package under `IncludExcludeEndpointFilter`. This filter is used by both the `OnAvailableEndpointCondition` and the auto-configuration classes. Fixes gh-21044 --- .../ExposeExcludePropertyEndpointFilter.java | 111 +------- .../OnAvailableEndpointCondition.java | 87 +++---- .../expose/IncludExcludeEndpointFilter.java | 237 ++++++++++++++++++ .../endpoint/expose/package-info.java | 20 ++ .../jmx/JmxEndpointAutoConfiguration.java | 8 +- ...ndpointManagementContextConfiguration.java | 8 +- .../web/WebEndpointAutoConfiguration.java | 15 +- ...oseExcludePropertyEndpointFilterTests.java | 1 + .../ConditionalOnAvailableEndpointTests.java | 32 ++- .../IncludExcludeEndpointFilterTests.java | 178 +++++++++++++ .../WebEndpointAutoConfigurationTests.java | 4 +- 11 files changed, 521 insertions(+), 180 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilterTests.java 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 4e53546f32f..0d8ea77f5c6 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 @@ -16,21 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.endpoint; -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.Set; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointFilter; -import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.Environment; -import org.springframework.util.Assert; /** * {@link EndpointFilter} that will filter endpoints based on {@code include} and @@ -39,108 +30,20 @@ import org.springframework.util.Assert; * @param the endpoint type * @author Phillip Webb * @since 2.0.0 + * @deprecated since 2.2.7 in favor of {@link IncludExcludeEndpointFilter} */ -public class ExposeExcludePropertyEndpointFilter> implements EndpointFilter { - - private final Class endpointType; - - private final EndpointPatterns include; - - private final EndpointPatterns exclude; - - private final EndpointPatterns exposeDefaults; +@Deprecated +public class ExposeExcludePropertyEndpointFilter> + extends IncludExcludeEndpointFilter { public ExposeExcludePropertyEndpointFilter(Class endpointType, Environment environment, String prefix, String... exposeDefaults) { - Assert.notNull(endpointType, "EndpointType must not be null"); - Assert.notNull(environment, "Environment must not be null"); - Assert.hasText(prefix, "Prefix must not be empty"); - Binder binder = Binder.get(environment); - this.endpointType = endpointType; - this.include = new EndpointPatterns(bind(binder, prefix + ".include")); - this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude")); - this.exposeDefaults = new EndpointPatterns(exposeDefaults); + super(endpointType, environment, prefix, 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 = new EndpointPatterns(include); - this.exclude = new EndpointPatterns(exclude); - this.exposeDefaults = new EndpointPatterns(exposeDefaults); - } - - private List bind(Binder binder, String name) { - return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new); - } - - @Override - public boolean match(E endpoint) { - if (this.endpointType.isInstance(endpoint)) { - return isExposed(endpoint) && !isExcluded(endpoint); - } - return true; - } - - private boolean isExposed(ExposableEndpoint endpoint) { - if (this.include.isEmpty()) { - return this.exposeDefaults.matchesAll() || this.exposeDefaults.matches(endpoint); - } - return this.include.matchesAll() || this.include.matches(endpoint); - } - - private boolean isExcluded(ExposableEndpoint endpoint) { - if (this.exclude.isEmpty()) { - return false; - } - return this.exclude.matchesAll() || this.exclude.matches(endpoint); - } - - /** - * 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()); - } - + super(endpointType, include, exclude, exposeDefaults); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java index b13faff3d78..7e274cb5f75 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.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. @@ -16,20 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Set; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter.DefaultIncludes; import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.cloud.CloudPlatform; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.ConcurrentReferenceHashMap; @@ -39,13 +37,14 @@ import org.springframework.util.ConcurrentReferenceHashMap; * * @author Brian Clozel * @author Stephane Nicoll + * @author Phillip Webb * @see ConditionalOnAvailableEndpoint */ class OnAvailableEndpointCondition extends AbstractEndpointCondition { private static final String JMX_ENABLED_KEY = "spring.jmx.enabled"; - private static final ConcurrentReferenceHashMap> endpointExposureCache = new ConcurrentReferenceHashMap<>(); + private static final Map> exposuresCache = new ConcurrentReferenceHashMap<>(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { @@ -60,79 +59,51 @@ class OnAvailableEndpointCondition extends AbstractEndpointCondition { return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class) .because("application is running on Cloud Foundry")); } - AnnotationAttributes attributes = getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, - metadata); - EndpointId id = EndpointId.of(environment, attributes.getString("id")); - Set exposureInformations = getExposureInformation(environment); - for (ExposureInformation exposureInformation : exposureInformations) { - if (exposureInformation.isExposed(id)) { + EndpointId id = EndpointId.of(environment, + getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata).getString("id")); + Set exposures = getExposures(environment); + for (Exposure exposure : exposures) { + if (exposure.isExposed(id)) { return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class) - .because("marked as exposed by a 'management.endpoints." - + exposureInformation.getPrefix() + ".exposure' property")); + .because("marked as exposed by a 'management.endpoints." + exposure.getPrefix() + + ".exposure' property")); } } return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class) .because("no 'management.endpoints' property marked it as exposed")); } - private Set getExposureInformation(Environment environment) { - Set exposureInformations = endpointExposureCache.get(environment); - if (exposureInformations == null) { - exposureInformations = new HashSet<>(2); - Binder binder = Binder.get(environment); + private Set getExposures(Environment environment) { + Set exposures = exposuresCache.get(environment); + if (exposures == null) { + exposures = new HashSet<>(2); if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) { - exposureInformations.add(new ExposureInformation(binder, "jmx", "*")); + exposures.add(new Exposure(environment, "jmx", DefaultIncludes.JMX)); } - exposureInformations.add(new ExposureInformation(binder, "web", "info", "health")); - endpointExposureCache.put(environment, exposureInformations); + exposures.add(new Exposure(environment, "web", DefaultIncludes.WEB)); + exposuresCache.put(environment, exposures); } - return exposureInformations; + return exposures; } - static class ExposureInformation { + static class Exposure extends IncludExcludeEndpointFilter> { private final String prefix; - private final Set include; - - private final Set exclude; - - private final Set exposeDefaults; - - ExposureInformation(Binder binder, String prefix, String... exposeDefaults) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Exposure(Environment environment, String prefix, DefaultIncludes defaultIncludes) { + super((Class) ExposableEndpoint.class, environment, "management.endpoints." + prefix + ".exposure", + defaultIncludes); this.prefix = prefix; - this.include = bind(binder, "management.endpoints." + prefix + ".exposure.include"); - this.exclude = bind(binder, "management.endpoints." + prefix + ".exposure.exclude"); - this.exposeDefaults = new HashSet<>(Arrays.asList(exposeDefaults)); - } - - private Set bind(Binder binder, String name) { - List values = binder.bind(name, Bindable.listOf(String.class)).orElse(Collections.emptyList()); - Set result = new HashSet<>(values.size()); - for (String value : values) { - result.add("*".equals(value) ? "*" : EndpointId.fromPropertyValue(value).toLowerCaseString()); - } - return result; } String getPrefix() { return this.prefix; } - boolean isExposed(EndpointId endpointId) { - String id = endpointId.toLowerCaseString(); - if (!this.exclude.isEmpty()) { - if (this.exclude.contains("*") || this.exclude.contains(id)) { - return false; - } - } - if (this.include.isEmpty()) { - if (this.exposeDefaults.contains("*") || this.exposeDefaults.contains(id)) { - return true; - } - } - return this.include.contains("*") || this.include.contains(id); + boolean isExposed(EndpointId id) { + return super.match(id); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilter.java new file mode 100644 index 00000000000..240c2b62116 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilter.java @@ -0,0 +1,237 @@ +/* + * 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. + * 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.boot.actuate.autoconfigure.endpoint.expose; + +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.Set; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +/** + * {@link EndpointFilter} that will filter endpoints based on {@code include} and + * {@code exclude} patterns. + * + * @param the endpoint type + * @author Phillip Webb + * @since 2.2.7 + */ +public class IncludExcludeEndpointFilter> implements EndpointFilter { + + private final Class endpointType; + + private final EndpointPatterns include; + + private final EndpointPatterns defaultIncludes; + + private final EndpointPatterns exclude; + + /** + * Create a new {@link IncludExcludeEndpointFilter} with include/exclude rules bound + * from the {@link Environment}. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param environment the environment containing the properties + * @param prefix the property prefix to bind + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + String... defaultIncludes) { + this(endpointType, environment, prefix, new EndpointPatterns(defaultIncludes)); + } + + /** + * Create a new {@link IncludExcludeEndpointFilter} with include/exclude rules bound + * from the {@link Environment}. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param environment the environment containing the properties + * @param prefix the property prefix to bind + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + DefaultIncludes defaultIncludes) { + this(endpointType, environment, prefix, DefaultIncludes.patterns(defaultIncludes)); + } + + private IncludExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + EndpointPatterns defaultIncludes) { + Assert.notNull(endpointType, "EndpointType must not be null"); + Assert.notNull(environment, "Environment must not be null"); + Assert.hasText(prefix, "Prefix must not be empty"); + Assert.notNull(defaultIncludes, "DefaultIncludes must not be null"); + Binder binder = Binder.get(environment); + this.endpointType = endpointType; + this.include = new EndpointPatterns(bind(binder, prefix + ".include")); + this.defaultIncludes = defaultIncludes; + this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude")); + } + + /** + * Create a new {@link IncludExcludeEndpointFilter} with specific include/exclude + * rules. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param include the include patterns + * @param exclude the exclude patterns + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + String... defaultIncludes) { + this(endpointType, include, exclude, new EndpointPatterns(defaultIncludes)); + } + + /** + * Create a new {@link IncludExcludeEndpointFilter} with specific include/exclude + * rules. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param include the include patterns + * @param exclude the exclude patterns + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + DefaultIncludes defaultIncludes) { + this(endpointType, include, exclude, DefaultIncludes.patterns(defaultIncludes)); + } + + private IncludExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + EndpointPatterns defaultIncludes) { + Assert.notNull(endpointType, "EndpointType Type must not be null"); + Assert.notNull(defaultIncludes, "DefaultIncludes must not be null"); + this.endpointType = endpointType; + this.include = new EndpointPatterns(include); + this.defaultIncludes = defaultIncludes; + this.exclude = new EndpointPatterns(exclude); + } + + private List bind(Binder binder, String name) { + return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new); + } + + @Override + public boolean match(E endpoint) { + if (!this.endpointType.isInstance(endpoint)) { + // Leave non-matching types for other filters + return true; + } + return match(endpoint.getEndpointId()); + } + + /** + * Return {@code true} if the filter matches. + * @param endpointId the endpoint ID to check + * @return {@code true} if the filter matches + */ + protected final boolean match(EndpointId endpointId) { + return isIncluded(endpointId) && !isExcluded(endpointId); + } + + private boolean isIncluded(EndpointId endpointId) { + if (this.include.isEmpty()) { + return this.defaultIncludes.matches(endpointId); + } + return this.include.matches(endpointId); + } + + private boolean isExcluded(EndpointId endpointId) { + if (this.exclude.isEmpty()) { + return false; + } + return this.exclude.matches(endpointId); + } + + /** + * Default include patterns that can be used. + */ + public enum DefaultIncludes { + + /** + * The default set of include patterns used for JMX. + */ + JMX("*"), + + /** + * The default set of include patterns used for web. + */ + WEB("info", "health"); + + private final EndpointPatterns patterns; + + DefaultIncludes(String... patterns) { + this.patterns = new EndpointPatterns(patterns); + } + + static EndpointPatterns patterns(DefaultIncludes defaultIncludes) { + return (defaultIncludes != null) ? defaultIncludes.patterns : (EndpointPatterns) null; + } + + } + + /** + * 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 matches(EndpointId endpointId) { + return this.matchesAll || this.endpointIds.contains(endpointId); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/package-info.java new file mode 100644 index 00000000000..023f6306ba8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + * 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. + */ + +/** + * Endpoint exposure logic used for auto-configuration and conditions. + */ +package org.springframework.boot.actuate.autoconfigure.endpoint.expose; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java index e2af732276b..a85f57cab9a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.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. @@ -23,7 +23,7 @@ import javax.management.MBeanServer; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; @@ -97,9 +97,9 @@ public class JmxEndpointAutoConfiguration { } @Bean - public ExposeExcludePropertyEndpointFilter jmxIncludeExcludePropertyEndpointFilter() { + public IncludExcludeEndpointFilter jmxIncludeExcludePropertyEndpointFilter() { JmxEndpointProperties.Exposure exposure = this.properties.getExposure(); - return new ExposeExcludePropertyEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(), + return new IncludExcludeEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(), exposure.getExclude(), "*"); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java index 2255a262c26..be7c01ce03d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.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. @@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; import org.glassfish.jersey.server.ResourceConfig; -import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; @@ -47,10 +47,10 @@ import org.springframework.web.servlet.DispatcherServlet; public class ServletEndpointManagementContextConfiguration { @Bean - public ExposeExcludePropertyEndpointFilter servletExposeExcludePropertyEndpointFilter( + public IncludExcludeEndpointFilter servletExposeExcludePropertyEndpointFilter( WebEndpointProperties properties) { WebEndpointProperties.Exposure exposure = properties.getExposure(); - return new ExposeExcludePropertyEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(), + return new IncludExcludeEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index ea5eac25d2b..0cf1ae247ea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.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. @@ -22,7 +22,8 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter.DefaultIncludes; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -112,16 +113,16 @@ public class WebEndpointAutoConfiguration { } @Bean - public ExposeExcludePropertyEndpointFilter webExposeExcludePropertyEndpointFilter() { + public IncludExcludeEndpointFilter webExposeExcludePropertyEndpointFilter() { WebEndpointProperties.Exposure exposure = this.properties.getExposure(); - return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(), - exposure.getExclude(), "info", "health"); + return new IncludExcludeEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(), + exposure.getExclude(), DefaultIncludes.WEB); } @Bean - public ExposeExcludePropertyEndpointFilter controllerExposeExcludePropertyEndpointFilter() { + public IncludExcludeEndpointFilter controllerExposeExcludePropertyEndpointFilter() { WebEndpointProperties.Exposure exposure = this.properties.getExposure(); - return new ExposeExcludePropertyEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(), + return new IncludExcludeEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(), exposure.getExclude()); } 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 d4a9a6b034e..5f456faf725 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 @@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock; * * @author Phillip Webb */ +@Deprecated class ExposeExcludePropertyEndpointFilterTests { private ExposeExcludePropertyEndpointFilter filter; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java index 41acd56904f..9230e66ae73 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.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. @@ -182,6 +182,20 @@ class ConditionalOnAvailableEndpointTests { (context) -> assertThat(context).hasBean("info").hasBean("health").hasBean("spring").hasBean("test")); } + @Test // gh-21044 + void outcomeWhenIncludeAllShouldMatchDashedEndpoint() throws Exception { + this.contextRunner.withUserConfiguration(DashedEndpointConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=*") + .run((context) -> assertThat(context).hasSingleBean(DashedEndpoint.class)); + } + + @Test // gh-21044 + void outcomeWhenIncludeDashedShouldMatchDashedEndpoint() throws Exception { + this.contextRunner.withUserConfiguration(DashedEndpointConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=test-dashed") + .run((context) -> assertThat(context).hasSingleBean(DashedEndpoint.class)); + } + @Endpoint(id = "health") static class HealthEndpoint { @@ -207,6 +221,11 @@ class ConditionalOnAvailableEndpointTests { } + @Endpoint(id = "test-dashed") + static class DashedEndpoint { + + } + @EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class) static class SpringEndpointExtension { @@ -284,4 +303,15 @@ class ConditionalOnAvailableEndpointTests { } + @Configuration(proxyBeanMethods = false) + static class DashedEndpointConfiguration { + + @Bean + @ConditionalOnAvailableEndpoint + DashedEndpoint dashedEndpoint() { + return new DashedEndpoint(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilterTests.java new file mode 100644 index 00000000000..fb9e61a50e6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludExcludeEndpointFilterTests.java @@ -0,0 +1,178 @@ +/* + * 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. + * 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.boot.actuate.autoconfigure.endpoint.expose; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link IncludExcludeEndpointFilter}. + * + * @author Phillip Webb + */ +class IncludExcludeEndpointFilterTests { + + private IncludExcludeEndpointFilter filter; + + @BeforeEach + void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + void createWhenEndpointTypeIsNullShouldThrowException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new IncludExcludeEndpointFilter<>(null, new MockEnvironment(), "foo")) + .withMessageContaining("EndpointType must not be null"); + } + + @Test + void createWhenEnvironmentIsNullShouldThrowException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, null, "foo")) + .withMessageContaining("Environment must not be null"); + } + + @Test + void createWhenPrefixIsNullShouldThrowException() { + assertThatIllegalArgumentException() + .isThrownBy( + () -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, new MockEnvironment(), null)) + .withMessageContaining("Prefix must not be empty"); + } + + @Test + void createWhenPrefixIsEmptyShouldThrowException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, new MockEnvironment(), "")) + .withMessageContaining("Prefix must not be empty"); + } + + @Test + void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch() { + setupFilter("", ""); + assertThat(match(EndpointId.of("def"))).isTrue(); + } + + @Test + void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch() { + setupFilter("", ""); + assertThat(match(EndpointId.of("bar"))).isFalse(); + } + + @Test + void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() { + setupFilter("bar", ""); + assertThat(match(EndpointId.of("bar"))).isTrue(); + } + + @Test + void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch() { + setupFilter("bar", ""); + assertThat(match(EndpointId.of("baz"))).isFalse(); + } + + @Test + void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() { + setupFilter("bar,baz", "baz"); + assertThat(match(EndpointId.of("baz"))).isFalse(); + } + + @Test + void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch() { + setupFilter("bar,baz", "buz"); + assertThat(match(EndpointId.of("baz"))).isTrue(); + } + + @Test + void matchWhenExposeMatchesWithDifferentCaseShouldMatch() { + setupFilter("bar", ""); + assertThat(match(EndpointId.of("bAr"))).isTrue(); + } + + @Test + void matchWhenDiscovererDoesNotMatchShouldMatch() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("foo.include", "bar"); + environment.setProperty("foo.exclude", ""); + this.filter = new IncludExcludeEndpointFilter<>(DifferentTestExposableWebEndpoint.class, environment, "foo"); + assertThat(match(EndpointId.of("baz"))).isTrue(); + } + + @Test + void matchWhenIncludeIsAsteriskShouldMatchAll() { + setupFilter("*", "buz"); + assertThat(match(EndpointId.of("bar"))).isTrue(); + assertThat(match(EndpointId.of("baz"))).isTrue(); + assertThat(match(EndpointId.of("buz"))).isFalse(); + } + + @Test + void matchWhenExcludeIsAsteriskShouldMatchNone() { + setupFilter("bar,baz,buz", "*"); + assertThat(match(EndpointId.of("bar"))).isFalse(); + assertThat(match(EndpointId.of("baz"))).isFalse(); + assertThat(match(EndpointId.of("buz"))).isFalse(); + } + + @Test + void matchWhenMixedCaseShouldMatch() { + setupFilter("foo-bar", ""); + assertThat(match(EndpointId.of("fooBar"))).isTrue(); + } + + @Test // gh-20997 + 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); + environment.setProperty("foo.exclude", exclude); + this.filter = new IncludExcludeEndpointFilter<>(TestExposableWebEndpoint.class, environment, "foo", "def"); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean match(EndpointId id) { + ExposableEndpoint endpoint = mock(TestExposableWebEndpoint.class); + given(endpoint.getEndpointId()).willReturn(id); + return ((EndpointFilter) this.filter).match(endpoint); + } + + abstract static class TestExposableWebEndpoint implements ExposableWebEndpoint { + + } + + abstract static class DifferentTestExposableWebEndpoint implements ExposableWebEndpoint { + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java index 89fc5ae40c7..941a7881930 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java @@ -24,7 +24,7 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; @@ -104,7 +104,7 @@ class WebEndpointAutoConfigurationTests { @Test void webApplicationConfiguresExposeExcludePropertyEndpointFilter() { this.contextRunner - .run((context) -> assertThat(context).getBeans(ExposeExcludePropertyEndpointFilter.class).containsKeys( + .run((context) -> assertThat(context).getBeans(IncludExcludeEndpointFilter.class).containsKeys( "webExposeExcludePropertyEndpointFilter", "controllerExposeExcludePropertyEndpointFilter")); }