Support role-based sanitization for actuator endpoints

Closes gh-32156
This commit is contained in:
Madhura Bhave
2022-07-21 12:59:44 -07:00
parent ada2450483
commit 47effdcade
43 changed files with 1213 additions and 968 deletions

View File

@@ -55,6 +55,7 @@ import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
@@ -83,11 +84,11 @@ import org.springframework.util.StringUtils;
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans.
*
* <p>
* To protect sensitive information from being exposed, certain property values are masked
* if their names end with a set of configurable values (default "password" and "secret").
* Configure property names by using
* {@code management.endpoint.configprops.keys-to-sanitize} in your Spring Boot
* application configuration.
* To protect sensitive information from being exposed, all property values are masked by
* default. To configure when property values should be shown, use
* {@code management.endpoint.configprops.show-values} and
* {@code management.endpoint.configprops.roles} in your Spring Boot application
* configuration.
*
* @author Christian Dupuis
* @author Dave Syer
@@ -104,16 +105,15 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private final Sanitizer sanitizer;
private final Show showValues;
private ApplicationContext context;
private ObjectMapper objectMapper;
public ConfigurationPropertiesReportEndpoint() {
this(Collections.emptyList());
}
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions) {
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions, Show showValues) {
this.sanitizer = new Sanitizer(sanitizingFunctions);
this.showValues = showValues;
}
@Override
@@ -121,31 +121,35 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
this.context = context;
}
public void setKeysToSanitize(String... keysToSanitize) {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
public void keysToSanitize(String... keysToSanitize) {
this.sanitizer.keysToSanitize(keysToSanitize);
}
@ReadOperation
public ApplicationConfigurationProperties configurationProperties() {
return extract(this.context, (bean) -> true);
boolean showUnsanitized = this.showValues.isShown(true);
return getConfigurationProperties(showUnsanitized);
}
ApplicationConfigurationProperties getConfigurationProperties(boolean showUnsanitized) {
return getConfigurationProperties(this.context, (bean) -> true, showUnsanitized);
}
@ReadOperation
public ApplicationConfigurationProperties configurationPropertiesWithPrefix(@Selector String prefix) {
return extract(this.context, (bean) -> bean.getAnnotation().prefix().startsWith(prefix));
boolean showUnsanitized = this.showValues.isShown(true);
return getConfigurationProperties(prefix, showUnsanitized);
}
private ApplicationConfigurationProperties extract(ApplicationContext context,
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
ApplicationConfigurationProperties getConfigurationProperties(String prefix, boolean showUnsanitized) {
return getConfigurationProperties(this.context, (bean) -> bean.getAnnotation().prefix().startsWith(prefix),
showUnsanitized);
}
private ApplicationConfigurationProperties getConfigurationProperties(ApplicationContext context,
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
ObjectMapper mapper = getObjectMapper();
Map<String, ContextConfigurationProperties> contexts = new HashMap<>();
ApplicationContext target = context;
while (target != null) {
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate));
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate, showUnsanitized));
target = target.getParent();
}
return new ApplicationConfigurationProperties(contexts);
@@ -196,20 +200,21 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context,
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values().stream()
.filter(beanFilterPredicate)
.collect(Collectors.toMap(ConfigurationPropertiesBean::getName, (bean) -> describeBean(mapper, bean)));
.filter(beanFilterPredicate).collect(Collectors.toMap(ConfigurationPropertiesBean::getName,
(bean) -> describeBean(mapper, bean, showUnsanitized)));
return new ContextConfigurationProperties(descriptors,
(context.getParent() != null) ? context.getParent().getId() : null);
}
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean) {
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean,
boolean showUnsanitized) {
String prefix = bean.getAnnotation().prefix();
Map<String, Object> serialized = safeSerialize(mapper, bean.getInstance(), prefix);
Map<String, Object> properties = sanitize(prefix, serialized);
Map<String, Object> inputs = getInputs(prefix, serialized);
Map<String, Object> properties = sanitize(prefix, serialized, showUnsanitized);
Map<String, Object> inputs = getInputs(prefix, serialized, showUnsanitized);
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
}
@@ -236,35 +241,36 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
* information.
* @param prefix the property prefix
* @param map the source map
* @param showUnsanitized whether to show the unsanitized values
* @return the sanitized map
*/
@SuppressWarnings("unchecked")
private Map<String, Object> sanitize(String prefix, Map<String, Object> map) {
private Map<String, Object> sanitize(String prefix, Map<String, Object> map, boolean showUnsanitized) {
map.forEach((key, value) -> {
String qualifiedKey = getQualifiedKey(prefix, key);
if (value instanceof Map) {
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value));
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value, showUnsanitized));
}
else if (value instanceof List) {
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
map.put(key, sanitize(qualifiedKey, (List<Object>) value, showUnsanitized));
}
else {
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value));
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value, showUnsanitized));
}
});
return map;
}
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value) {
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value, boolean showUnsanitized) {
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
ConfigurationProperty candidate = getCandidate(currentName);
PropertySource<?> propertySource = getPropertySource(candidate);
if (propertySource != null) {
SanitizableData data = new SanitizableData(propertySource, qualifiedKey, value);
return this.sanitizer.sanitize(data);
return this.sanitizer.sanitize(data, showUnsanitized);
}
SanitizableData data = new SanitizableData(null, qualifiedKey, value);
return this.sanitizer.sanitize(data);
return this.sanitizer.sanitize(data, showUnsanitized);
}
private PropertySource<?> getPropertySource(ConfigurationProperty configurationProperty) {
@@ -293,69 +299,69 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
@SuppressWarnings("unchecked")
private List<Object> sanitize(String prefix, List<Object> list) {
private List<Object> sanitize(String prefix, List<Object> list, boolean showUnsanitized) {
List<Object> sanitized = new ArrayList<>();
int index = 0;
for (Object item : list) {
String name = prefix + "[" + index++ + "]";
if (item instanceof Map) {
sanitized.add(sanitize(name, (Map<String, Object>) item));
sanitized.add(sanitize(name, (Map<String, Object>) item, showUnsanitized));
}
else if (item instanceof List) {
sanitized.add(sanitize(name, (List<Object>) item));
sanitized.add(sanitize(name, (List<Object>) item, showUnsanitized));
}
else {
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item));
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item, showUnsanitized));
}
}
return sanitized;
}
@SuppressWarnings("unchecked")
private Map<String, Object> getInputs(String prefix, Map<String, Object> map) {
private Map<String, Object> getInputs(String prefix, Map<String, Object> map, boolean showUnsanitized) {
Map<String, Object> augmented = new LinkedHashMap<>(map);
map.forEach((key, value) -> {
String qualifiedKey = getQualifiedKey(prefix, key);
if (value instanceof Map) {
augmented.put(key, getInputs(qualifiedKey, (Map<String, Object>) value));
augmented.put(key, getInputs(qualifiedKey, (Map<String, Object>) value, showUnsanitized));
}
else if (value instanceof List) {
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value));
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value, showUnsanitized));
}
else {
augmented.put(key, applyInput(qualifiedKey));
augmented.put(key, applyInput(qualifiedKey, showUnsanitized));
}
});
return augmented;
}
@SuppressWarnings("unchecked")
private List<Object> getInputs(String prefix, List<Object> list) {
private List<Object> getInputs(String prefix, List<Object> list, boolean showUnsanitized) {
List<Object> augmented = new ArrayList<>();
int index = 0;
for (Object item : list) {
String name = prefix + "[" + index++ + "]";
if (item instanceof Map) {
augmented.add(getInputs(name, (Map<String, Object>) item));
augmented.add(getInputs(name, (Map<String, Object>) item, showUnsanitized));
}
else if (item instanceof List) {
augmented.add(getInputs(name, (List<Object>) item));
augmented.add(getInputs(name, (List<Object>) item, showUnsanitized));
}
else {
augmented.add(applyInput(name));
augmented.add(applyInput(name, showUnsanitized));
}
}
return augmented;
}
private Map<String, Object> applyInput(String qualifiedKey) {
private Map<String, Object> applyInput(String qualifiedKey, boolean showUnsanitized) {
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
ConfigurationProperty candidate = getCandidate(currentName);
PropertySource<?> propertySource = getPropertySource(candidate);
if (propertySource != null) {
Object value = stringifyIfNecessary(candidate.getValue());
SanitizableData data = new SanitizableData(propertySource, currentName.toString(), value);
return getInput(candidate, this.sanitizer.sanitize(data));
return getInput(candidate, this.sanitizer.sanitize(data, showUnsanitized));
}
return Collections.emptyMap();
}
@@ -550,7 +556,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private final Map<String, ContextConfigurationProperties> contexts;
private ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
this.contexts = contexts;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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,7 +16,11 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Set;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@@ -34,15 +38,29 @@ public class ConfigurationPropertiesReportEndpointWebExtension {
private final ConfigurationPropertiesReportEndpoint delegate;
public ConfigurationPropertiesReportEndpointWebExtension(ConfigurationPropertiesReportEndpoint delegate) {
private final Show showValues;
private final Set<String> roles;
public ConfigurationPropertiesReportEndpointWebExtension(ConfigurationPropertiesReportEndpoint delegate,
Show showValues, Set<String> roles) {
this.delegate = delegate;
this.showValues = showValues;
this.roles = roles;
}
@ReadOperation
public ApplicationConfigurationProperties configurationProperties(SecurityContext securityContext) {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
return this.delegate.getConfigurationProperties(showUnsanitized);
}
@ReadOperation
public WebEndpointResponse<ApplicationConfigurationProperties> configurationPropertiesWithPrefix(
@Selector String prefix) {
ApplicationConfigurationProperties configurationProperties = this.delegate
.configurationPropertiesWithPrefix(prefix);
SecurityContext securityContext, @Selector String prefix) {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
ApplicationConfigurationProperties configurationProperties = this.delegate.getConfigurationProperties(prefix,
showUnsanitized);
boolean foundMatchingBeans = configurationProperties.getContexts().values().stream()
.anyMatch((context) -> !context.getBeans().isEmpty());
return (foundMatchingBeans) ? new WebEndpointResponse<>(configurationProperties, WebEndpointResponse.STATUS_OK)

View File

@@ -17,17 +17,8 @@
package org.springframework.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Strategy that should be used by endpoint implementations to sanitize potentially
@@ -46,148 +37,40 @@ import org.springframework.util.StringUtils;
*/
public class Sanitizer {
private static final String[] REGEX_PARTS = { "*", "$", "^", "+" };
private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = new LinkedHashSet<>(
Arrays.asList("password", "secret", "key", "token", ".*credentials.*", "vcap_services",
"^vcap\\.services.*$", "sun.java.command", "^spring[._]application[._]json$"));
private static final Set<String> URI_USERINFO_KEYS = new LinkedHashSet<>(
Arrays.asList("uri", "uris", "url", "urls", "address", "addresses"));
private static final Pattern URI_USERINFO_PATTERN = Pattern
.compile("^\\[?[A-Za-z][A-Za-z0-9\\+\\.\\-]+://.+:(.*)@.+$");
private Pattern[] keysToSanitize;
private final List<SanitizingFunction> sanitizingFunctions = new ArrayList<>();
static {
DEFAULT_KEYS_TO_SANITIZE.addAll(URI_USERINFO_KEYS);
}
/**
* Create a new {@link Sanitizer} instance with a default set of keys to sanitize.
* Create a new {@link Sanitizer} instance.
*/
public Sanitizer() {
this(DEFAULT_KEYS_TO_SANITIZE.toArray(new String[0]));
this(Collections.emptyList());
}
/**
* Create a new {@link Sanitizer} instance with specific keys to sanitize.
* @param keysToSanitize the keys to sanitize
*/
public Sanitizer(String... keysToSanitize) {
this(Collections.emptyList(), keysToSanitize);
}
/**
* Create a new {@link Sanitizer} instance with a default set of keys to sanitize and
* additional sanitizing functions.
* Create a new {@link Sanitizer} instance with sanitizing functions.
* @param sanitizingFunctions the sanitizing functions to apply
* @since 2.6.0
*/
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions) {
this(sanitizingFunctions, DEFAULT_KEYS_TO_SANITIZE.toArray(new String[0]));
}
/**
* Create a new {@link Sanitizer} instance with specific keys to sanitize and
* additional sanitizing functions.
* @param sanitizingFunctions the sanitizing functions to apply
* @param keysToSanitize the keys to sanitize
* @since 2.6.0
*/
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions, String... keysToSanitize) {
sanitizingFunctions.forEach(this.sanitizingFunctions::add);
this.sanitizingFunctions.add(getDefaultSanitizingFunction());
setKeysToSanitize(keysToSanitize);
}
private SanitizingFunction getDefaultSanitizingFunction() {
return (data) -> {
Object sanitizedValue = sanitize(data.getKey(), data.getValue());
return data.withValue(sanitizedValue);
};
}
/**
* Set the keys that should be sanitized, overwriting any existing configuration. Keys
* can be simple strings that the property ends with or regular expressions.
* @param keysToSanitize the keys to sanitize
*/
public void setKeysToSanitize(String... keysToSanitize) {
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
this.keysToSanitize = new Pattern[keysToSanitize.length];
for (int i = 0; i < keysToSanitize.length; i++) {
this.keysToSanitize[i] = getPattern(keysToSanitize[i]);
}
}
/**
* Adds keys that should be sanitized. Keys can be simple strings that the property
* ends with or regular expressions.
* @param keysToSanitize the keys to sanitize
* @since 2.5.0
*/
public void keysToSanitize(String... keysToSanitize) {
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
int existingKeys = this.keysToSanitize.length;
this.keysToSanitize = Arrays.copyOf(this.keysToSanitize, this.keysToSanitize.length + keysToSanitize.length);
for (int i = 0; i < keysToSanitize.length; i++) {
this.keysToSanitize[i + existingKeys] = getPattern(keysToSanitize[i]);
}
}
private Pattern getPattern(String value) {
if (isRegex(value)) {
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
}
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
}
private boolean isRegex(String value) {
for (String part : REGEX_PARTS) {
if (value.contains(part)) {
return true;
}
}
return false;
}
/**
* Sanitize the given value if necessary.
* @param key the key to sanitize
* @param value the value
* @return the potentially sanitized value
*/
public Object sanitize(String key, Object value) {
if (value == null) {
return null;
}
for (Pattern pattern : this.keysToSanitize) {
if (pattern.matcher(key).matches()) {
if (keyIsUriWithUserInfo(pattern)) {
return sanitizeUris(value.toString());
}
return SanitizableData.SANITIZED_VALUE;
}
}
return value;
}
/**
* Sanitize the value from the given {@link SanitizableData} using the available
* {@link SanitizingFunction}s.
* @param data the sanitizable data
* @param showUnsanitized whether to show the unsanitized values or not
* @return the potentially updated data
* @since 2.6.0
* @since 3.0.0
*/
public Object sanitize(SanitizableData data) {
public Object sanitize(SanitizableData data, boolean showUnsanitized) {
Object value = data.getValue();
if (value == null) {
return null;
}
if (!showUnsanitized) {
return SanitizableData.SANITIZED_VALUE;
}
for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) {
data = sanitizingFunction.apply(data);
Object sanitizedValue = data.getValue();
@@ -198,26 +81,4 @@ public class Sanitizer {
return value;
}
private boolean keyIsUriWithUserInfo(Pattern pattern) {
for (String uriKey : URI_USERINFO_KEYS) {
if (pattern.matcher(uriKey).matches()) {
return true;
}
}
return false;
}
private Object sanitizeUris(String value) {
return Arrays.stream(value.split(",")).map(this::sanitizeUri).collect(Collectors.joining(","));
}
private String sanitizeUri(String value) {
Matcher matcher = URI_USERINFO_PATTERN.matcher(value);
String password = matcher.matches() ? matcher.group(1) : null;
if (password != null) {
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
}
return value;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2012-2022 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.endpoint;
import java.security.Principal;
import java.util.Collection;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* Options for showing data in endpoint responses.
*
* @author Madhura Bhave
* @since 3.0.0
*/
public enum Show {
/**
* Never show the item in the response.
*/
NEVER,
/**
* Show the item in the response when accessed by an authorized user.
*/
WHEN_AUTHORIZED,
/**
* Always show the item in the response.
*/
ALWAYS;
/**
* Return if data should be shown when no {@link SecurityContext} is available.
* @param unauthorizedResult the result to used for an unauthorized user
* @return if data should be shown
*/
public boolean isShown(boolean unauthorizedResult) {
return switch (this) {
case NEVER -> false;
case ALWAYS -> true;
case WHEN_AUTHORIZED -> unauthorizedResult;
};
}
/**
* Return if data should be shown.
* @param securityContext the security context
* @param roles the required roles
* @return if data should be shown
*/
public boolean isShown(SecurityContext securityContext, Collection<String> roles) {
return switch (this) {
case NEVER -> false;
case ALWAYS -> true;
case WHEN_AUTHORIZED -> isAuthorized(securityContext, roles);
};
}
private boolean isAuthorized(SecurityContext securityContext, Collection<String> roles) {
Principal principal = securityContext.getPrincipal();
if (principal == null) {
return false;
}
if (CollectionUtils.isEmpty(roles)) {
return true;
}
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
for (String role : roles) {
if (securityContext.isUserInRole(role)) {
return true;
}
if (checkAuthorities) {
Authentication authentication = (Authentication) principal;
for (GrantedAuthority authority : authentication.getAuthorities()) {
String name = authority.getAuthority();
if (role.equals(name)) {
return true;
}
}
}
}
return false;
}
private boolean isSpringSecurityAuthentication(Principal principal) {
return ClassUtils.isPresent("org.springframework.security.core.Authentication", null)
&& (principal instanceof Authentication);
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.boot.actuate.env;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -31,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
@@ -48,9 +48,7 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;
/**
* {@link Endpoint @Endpoint} to expose {@link ConfigurableEnvironment environment}
@@ -71,50 +69,48 @@ public class EnvironmentEndpoint {
private final Environment environment;
public EnvironmentEndpoint(Environment environment) {
this(environment, Collections.emptyList());
}
private final Show showValues;
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions) {
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions,
Show showValues) {
this.environment = environment;
this.sanitizer = new Sanitizer(sanitizingFunctions);
}
public void setKeysToSanitize(String... keysToSanitize) {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
public void keysToSanitize(String... keysToSanitize) {
this.sanitizer.keysToSanitize(keysToSanitize);
this.showValues = showValues;
}
@ReadOperation
public EnvironmentDescriptor environment(@Nullable String pattern) {
boolean showUnsanitized = this.showValues.isShown(true);
return getEnvironmentDescriptor(pattern, showUnsanitized);
}
EnvironmentDescriptor getEnvironmentDescriptor(String pattern, boolean showUnsanitized) {
if (StringUtils.hasText(pattern)) {
return getEnvironmentDescriptor(Pattern.compile(pattern).asPredicate());
return getEnvironmentDescriptor(Pattern.compile(pattern).asPredicate(), showUnsanitized);
}
return getEnvironmentDescriptor((name) -> true);
return getEnvironmentDescriptor((name) -> true, showUnsanitized);
}
@ReadOperation
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
return getEnvironmentEntryDescriptor(toMatch);
}
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate) {
PlaceholdersResolver resolver = getResolver();
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate,
boolean showUnsanitized) {
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
getPropertySourcesAsMap().forEach((sourceName, source) -> {
if (source instanceof EnumerablePropertySource) {
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source, resolver,
propertyNamePredicate));
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source,
propertyNamePredicate, showUnsanitized));
}
});
return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), propertySources);
}
private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName) {
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(propertyName);
@ReadOperation
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
boolean showUnsanitized = this.showValues.isShown(true);
return getEnvironmentEntryDescriptor(toMatch, showUnsanitized);
}
EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName, boolean showUnsanitized) {
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(propertyName, showUnsanitized);
PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors);
return new EnvironmentEntryDescriptor(summary, Arrays.asList(this.environment.getActiveProfiles()),
toPropertySourceDescriptors(descriptors));
@@ -136,35 +132,31 @@ public class EnvironmentEndpoint {
return null;
}
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName) {
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName,
boolean showUnsanitized) {
Map<String, PropertyValueDescriptor> propertySources = new LinkedHashMap<>();
PlaceholdersResolver resolver = getResolver();
getPropertySourcesAsMap().forEach((sourceName, source) -> propertySources.put(sourceName,
source.containsProperty(propertyName) ? describeValueOf(propertyName, source, resolver) : null));
source.containsProperty(propertyName) ? describeValueOf(propertyName, source, showUnsanitized) : null));
return propertySources;
}
private PropertySourceDescriptor describeSource(String sourceName, EnumerablePropertySource<?> source,
PlaceholdersResolver resolver, Predicate<String> namePredicate) {
Predicate<String> namePredicate, boolean showUnsanitized) {
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
Stream.of(source.getPropertyNames()).filter(namePredicate)
.forEach((name) -> properties.put(name, describeValueOf(name, source, resolver)));
.forEach((name) -> properties.put(name, describeValueOf(name, source, showUnsanitized)));
return new PropertySourceDescriptor(sourceName, properties);
}
@SuppressWarnings("unchecked")
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source,
PlaceholdersResolver resolver) {
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source, boolean showUnsanitized) {
PlaceholdersResolver resolver = new PropertySourcesPlaceholdersResolver(getPropertySources());
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
Object sanitizedValue = sanitize(source, name, resolved);
Object sanitizedValue = sanitize(source, name, resolved, showUnsanitized);
return new PropertyValueDescriptor(stringifyIfNecessary(sanitizedValue), origin);
}
private PlaceholdersResolver getResolver() {
return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer);
}
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
for (PropertySource<?> source : getPropertySources()) {
@@ -193,8 +185,8 @@ public class EnvironmentEndpoint {
}
}
private Object sanitize(PropertySource<?> source, String name, Object value) {
return this.sanitizer.sanitize(new SanitizableData(source, name, value));
private Object sanitize(PropertySource<?> source, String name, Object value, boolean showUnsanitized) {
return this.sanitizer.sanitize(new SanitizableData(source, name, value), showUnsanitized);
}
protected Object stringifyIfNecessary(Object value) {
@@ -208,40 +200,6 @@ public class EnvironmentEndpoint {
return "Complex property type " + value.getClass().getName();
}
/**
* {@link PropertySourcesPlaceholdersResolver} that sanitizes sensitive placeholders
* if present.
*/
private static class PropertySourcesPlaceholdersSanitizingResolver extends PropertySourcesPlaceholdersResolver {
private final Sanitizer sanitizer;
private final Iterable<PropertySource<?>> sources;
PropertySourcesPlaceholdersSanitizingResolver(Iterable<PropertySource<?>> sources, Sanitizer sanitizer) {
super(sources, new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true));
this.sources = sources;
this.sanitizer = sanitizer;
}
@Override
protected String resolvePlaceholder(String placeholder) {
if (this.sources != null) {
for (PropertySource<?> source : this.sources) {
Object value = source.getProperty(placeholder);
if (value != null) {
SanitizableData data = new SanitizableData(source, placeholder, value);
Object sanitized = this.sanitizer.sanitize(data);
return (sanitized != null) ? String.valueOf(sanitized) : null;
}
}
}
return null;
}
}
/**
* A description of an {@link Environment}.
*/
@@ -278,7 +236,7 @@ public class EnvironmentEndpoint {
private final List<PropertySourceEntryDescriptor> propertySources;
private EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
List<PropertySourceEntryDescriptor> propertySources) {
this.property = property;
this.activeProfiles = activeProfiles;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2022 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,11 +16,17 @@
package org.springframework.boot.actuate.env;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
import org.springframework.lang.Nullable;
/**
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link EnvironmentEndpoint}.
@@ -34,13 +40,27 @@ public class EnvironmentEndpointWebExtension {
private final EnvironmentEndpoint delegate;
public EnvironmentEndpointWebExtension(EnvironmentEndpoint delegate) {
private final Show showValues;
private final Set<String> roles;
public EnvironmentEndpointWebExtension(EnvironmentEndpoint delegate, Show showValues, Set<String> roles) {
this.delegate = delegate;
this.showValues = showValues;
this.roles = roles;
}
@ReadOperation
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(@Selector String toMatch) {
EnvironmentEntryDescriptor descriptor = this.delegate.environmentEntry(toMatch);
public EnvironmentDescriptor environment(SecurityContext securityContext, @Nullable String pattern) {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
return this.delegate.getEnvironmentDescriptor(pattern, showUnsanitized);
}
@ReadOperation
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(SecurityContext securityContext,
@Selector String toMatch) {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
EnvironmentEntryDescriptor descriptor = this.delegate.getEnvironmentEntryDescriptor(toMatch, showUnsanitized);
return (descriptor.getProperty() != null) ? new WebEndpointResponse<>(descriptor, WebEndpointResponse.STATUS_OK)
: new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
}

View File

@@ -48,7 +48,9 @@ import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.util.Assert;
@@ -71,25 +73,10 @@ public class QuartzEndpoint {
private final Sanitizer sanitizer;
/**
* Create an instance for the specified {@link Scheduler} using a default
* {@link Sanitizer}.
* @param scheduler the scheduler to use to retrieve jobs and triggers details
*/
public QuartzEndpoint(Scheduler scheduler) {
this(scheduler, new Sanitizer());
}
/**
* Create an instance for the specified {@link Scheduler} and {@link Sanitizer}.
* @param scheduler the scheduler to use to retrieve jobs and triggers details
* @param sanitizer the sanitizer to use to sanitize data maps
*/
public QuartzEndpoint(Scheduler scheduler, Sanitizer sanitizer) {
public QuartzEndpoint(Scheduler scheduler, Iterable<SanitizingFunction> sanitizingFunctions) {
Assert.notNull(scheduler, "Scheduler must not be null");
Assert.notNull(sanitizer, "Sanitizer must not be null");
this.scheduler = scheduler;
this.sanitizer = sanitizer;
this.sanitizer = new Sanitizer(sanitizingFunctions);
}
/**
@@ -201,17 +188,19 @@ public class QuartzEndpoint {
* group name and job name.
* @param groupName the name of the group
* @param jobName the name of the job
* @param showUnsanitized whether to sanitize values in data map
* @return the details of the job or {@code null} if such job does not exist
* @throws SchedulerException if retrieving the information from the scheduler failed
*/
public QuartzJobDetails quartzJob(String groupName, String jobName) throws SchedulerException {
public QuartzJobDetails quartzJob(String groupName, String jobName, boolean showUnsanitized)
throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, groupName);
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
if (jobDetail != null) {
List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jobKey);
return new QuartzJobDetails(jobDetail.getKey().getGroup(), jobDetail.getKey().getName(),
jobDetail.getDescription(), jobDetail.getJobClass().getName(), jobDetail.isDurable(),
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap()),
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap(), showUnsanitized),
extractTriggersSummary(triggers));
}
return null;
@@ -236,14 +225,18 @@ public class QuartzEndpoint {
* name.
* @param groupName the name of the group
* @param triggerName the name of the trigger
* @param showUnsanitized whether to sanitize values in data map
* @return the details of the trigger or {@code null} if such trigger does not exist
* @throws SchedulerException if retrieving the information from the scheduler failed
*/
public Map<String, Object> quartzTrigger(String groupName, String triggerName) throws SchedulerException {
Map<String, Object> quartzTrigger(String groupName, String triggerName, boolean showUnsanitized)
throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, groupName);
Trigger trigger = this.scheduler.getTrigger(triggerKey);
return (trigger != null) ? TriggerDescription.of(trigger).buildDetails(
this.scheduler.getTriggerState(triggerKey), sanitizeJobDataMap(trigger.getJobDataMap())) : null;
return (trigger != null)
? TriggerDescription.of(trigger).buildDetails(this.scheduler.getTriggerState(triggerKey),
sanitizeJobDataMap(trigger.getJobDataMap(), showUnsanitized))
: null;
}
private static Duration getIntervalDuration(long amount, IntervalUnit unit) {
@@ -255,15 +248,20 @@ public class QuartzEndpoint {
: null;
}
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap) {
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap, boolean showUnsanitized) {
if (dataMap != null) {
Map<String, Object> map = new LinkedHashMap<>(dataMap.getWrappedMap());
map.replaceAll(this.sanitizer::sanitize);
map.replaceAll((key, value) -> getSanitizedValue(showUnsanitized, key, value));
return map;
}
return null;
}
private Object getSanitizedValue(boolean showUnsanitized, String key, Object value) {
SanitizableData data = new SanitizableData(null, key, value);
return this.sanitizer.sanitize(data, showUnsanitized);
}
private static TemporalUnit temporalUnit(IntervalUnit unit) {
return switch (unit) {
case DAY -> ChronoUnit.DAYS;

View File

@@ -16,10 +16,14 @@
package org.springframework.boot.actuate.quartz;
import java.util.Set;
import org.quartz.SchedulerException;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@@ -44,8 +48,14 @@ public class QuartzEndpointWebExtension {
private final QuartzEndpoint delegate;
public QuartzEndpointWebExtension(QuartzEndpoint delegate) {
private final Show showValues;
private final Set<String> roles;
public QuartzEndpointWebExtension(QuartzEndpoint delegate, Show showValues, Set<String> roles) {
this.delegate = delegate;
this.showValues = showValues;
this.roles = roles;
}
@ReadOperation
@@ -62,10 +72,11 @@ public class QuartzEndpointWebExtension {
}
@ReadOperation
public WebEndpointResponse<Object> quartzJobOrTrigger(@Selector String jobsOrTriggers, @Selector String group,
@Selector String name) throws SchedulerException {
return handle(jobsOrTriggers, () -> this.delegate.quartzJob(group, name),
() -> this.delegate.quartzTrigger(group, name));
public WebEndpointResponse<Object> quartzJobOrTrigger(SecurityContext securityContext,
@Selector String jobsOrTriggers, @Selector String group, @Selector String name) throws SchedulerException {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
return handle(jobsOrTriggers, () -> this.delegate.quartzJob(group, name, showUnsanitized),
() -> this.delegate.quartzTrigger(group, name, showUnsanitized));
}
private <T> WebEndpointResponse<T> handle(String jobsOrTriggers, ResponseSupplier<T> jobAction,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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,15 +16,21 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,16 +45,7 @@ class ConfigurationPropertiesReportEndpointFilteringTests {
void filterByPrefixSingleMatch() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withUserConfiguration(Config.class)
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationPropertiesWithPrefix("only.bar");
assertThat(applicationProperties.getContexts()).containsOnlyKeys(context.getId());
ContextConfigurationProperties contextProperties = applicationProperties.getContexts().get(context.getId());
assertThat(contextProperties.getBeans().values()).singleElement().hasFieldOrPropertyWithValue("prefix",
"only.bar");
});
assertProperties(contextRunner, "solo1");
}
@Test
@@ -81,15 +78,84 @@ class ConfigurationPropertiesReportEndpointFilteringTests {
});
}
@Test
void noSanitizationWhenShowAlways() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(ConfigWithAlways.class)
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
assertProperties(contextRunner, "solo1");
}
@Test
void sanitizationWhenShowNever() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(ConfigWithNever.class)
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
assertProperties(contextRunner, "******");
}
private void assertProperties(ApplicationContextRunner contextRunner, String value) {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationPropertiesWithPrefix("only.bar");
assertThat(applicationProperties.getContexts()).containsOnlyKeys(context.getId());
ContextConfigurationProperties contextProperties = applicationProperties.getContexts().get(context.getId());
Optional<String> key = contextProperties.getBeans().keySet().stream()
.filter((id) -> findIdFromPrefix("only.bar", id)).findAny();
ConfigurationPropertiesBeanDescriptor descriptor = contextProperties.getBeans().get(key.get());
assertThat(descriptor.getPrefix()).isEqualTo("only.bar");
assertThat(descriptor.getProperties().get("name")).isEqualTo(value);
});
}
private boolean findIdFromPrefix(String prefix, String id) {
int separator = id.indexOf("-");
String candidate = (separator != -1) ? id.substring(0, separator) : id;
return prefix.equals(candidate);
}
@Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class)
@EnableConfigurationProperties(Bar.class)
static class Config {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.WHEN_AUTHORIZED);
}
}
@Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class)
@EnableConfigurationProperties(Bar.class)
static class ConfigWithNever {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.NEVER);
}
}
@Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class)
@EnableConfigurationProperties(Bar.class)
static class ConfigWithAlways {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(Bar.class)
static class BaseConfiguration {
@Bean
@ConfigurationProperties(prefix = "foo.primary")
Foo primaryFoo() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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,11 +16,14 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -79,7 +82,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean
@@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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,9 +16,12 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -86,7 +89,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean
@@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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,6 +16,7 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Collections;
import java.util.Map;
import javax.sql.DataSource;
@@ -24,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -85,7 +87,7 @@ class ConfigurationPropertiesReportEndpointProxyTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean

View File

@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -290,7 +291,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
}
@@ -553,7 +554,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
}
@Bean

View File

@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -45,7 +46,6 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
@@ -175,82 +175,10 @@ class ConfigurationPropertiesReportEndpointTests {
(properties) -> assertThat(properties.get("mixedBoolean")).isEqualTo(true)));
}
@Test
void sanitizeWithDefaultSettings() {
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
.run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("******");
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
}));
}
@Test
void sanitizeWithCustomKey() {
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
.withPropertyValues("test.keys-to-sanitize=property").run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("123456");
assertThat(properties.get("myTestProperty")).isEqualTo("******");
}));
}
@Test
void sanitizeWithCustomKeyPattern() {
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
.withPropertyValues("test.keys-to-sanitize=.*pass.*").run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("******");
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
}));
}
@Test
void sanitizeWithCustomPatternUsingCompositeKeys() {
this.contextRunner.withUserConfiguration(Gh4415PropertiesConfiguration.class)
.withPropertyValues("test.keys-to-sanitize=.*\\.secrets\\..*,.*\\.hidden\\..*")
.run(assertProperties("gh4415", (properties) -> {
Map<String, Object> secrets = (Map<String, Object>) properties.get("secrets");
Map<String, Object> hidden = (Map<String, Object>) properties.get("hidden");
assertThat(secrets.get("mine")).isEqualTo("******");
assertThat(secrets.get("yours")).isEqualTo("******");
assertThat(hidden.get("mine")).isEqualTo("******");
}));
}
@Test
void sanitizeUriWithSensitiveInfo() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.withPropertyValues("sensible.sensitiveUri=http://user:password@localhost:8080")
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("sensitiveUri"))
.isEqualTo("http://user:******@localhost:8080"), (inputs) -> {
Map<String, Object> sensitiveUri = (Map<String, Object>) inputs.get("sensitiveUri");
assertThat(sensitiveUri.get("value")).isEqualTo("http://user:******@localhost:8080");
assertThat(sensitiveUri.get("origin"))
.isEqualTo("\"sensible.sensitiveUri\" from property source \"test\"");
}));
}
@Test
void sanitizeUriWithNoPassword() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.withPropertyValues("sensible.noPasswordUri=http://user:@localhost:8080")
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("noPasswordUri"))
.isEqualTo("http://user:******@localhost:8080"), (inputs) -> {
Map<String, Object> noPasswordUri = (Map<String, Object>) inputs.get("noPasswordUri");
assertThat(noPasswordUri.get("value")).isEqualTo("http://user:******@localhost:8080");
assertThat(noPasswordUri.get("origin"))
.isEqualTo("\"sensible.noPasswordUri\" from property source \"test\"");
}));
}
@Test
void sanitizeAddressesFieldContainingMultipleRawSensitiveUris() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("rawSensitiveAddresses"))
.isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082")));
}
@Test
void sanitizeLists() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
new ApplicationContextRunner()
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
.withPropertyValues("sensible.listItems[0].some-password=password")
.run(assertProperties("sensible", (properties) -> {
assertThat(properties.get("listItems")).isInstanceOf(List.class);
@@ -271,7 +199,8 @@ class ConfigurationPropertiesReportEndpointTests {
@Test
void listsOfListsAreSanitized() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
new ApplicationContextRunner()
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
.withPropertyValues("sensible.listOfListItems[0][0].some-password=password")
.run(assertProperties("sensible", (properties) -> {
assertThat(properties.get("listOfListItems")).isInstanceOf(List.class);
@@ -311,7 +240,7 @@ class ConfigurationPropertiesReportEndpointTests {
.withUserConfiguration(CustomSanitizingEndpointConfig.class,
PropertySourceBasedSanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
.withPropertyValues("test.my-test-property=abcde").run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("******");
assertThat(properties.get("dbPassword")).isEqualTo("123456");
assertThat(properties.get("myTestProperty")).isEqualTo("$$$");
}));
}
@@ -339,6 +268,26 @@ class ConfigurationPropertiesReportEndpointTests {
}));
}
@Test
void noSanitizationWhenShowAlways() {
new ApplicationContextRunner()
.withUserConfiguration(EndpointConfigWithShowAlways.class, TestPropertiesConfiguration.class)
.run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("123456");
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
}));
}
@Test
void sanitizationWhenShowNever() {
new ApplicationContextRunner()
.withUserConfiguration(EndpointConfigWithShowNever.class, TestPropertiesConfiguration.class)
.run(assertProperties("test", (properties) -> {
assertThat(properties.get("dbPassword")).isEqualTo("******");
assertThat(properties.get("myTestProperty")).isEqualTo("******");
}));
}
@Test
void originParents() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
@@ -367,8 +316,9 @@ class ConfigurationPropertiesReportEndpointTests {
return (context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ContextConfigurationProperties allProperties = endpoint.configurationProperties().getContexts()
.get(context.getId());
ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties configurationProperties = endpoint
.configurationProperties();
ContextConfigurationProperties allProperties = configurationProperties.getContexts().get(context.getId());
Optional<String> key = allProperties.getBeans().keySet().stream()
.filter((id) -> findIdFromPrefix(prefix, id)).findAny();
assertThat(key).describedAs("No configuration properties with prefix '%s' found", prefix).isPresent();
@@ -421,12 +371,33 @@ class ConfigurationPropertiesReportEndpointTests {
static class EndpointConfig {
@Bean
ConfigurationPropertiesReportEndpoint endpoint(Environment environment) {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
if (keys != null) {
endpoint.setKeysToSanitize(keys);
}
ConfigurationPropertiesReportEndpoint endpoint() {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
Collections.emptyList(), Show.WHEN_AUTHORIZED);
return endpoint;
}
}
@Configuration(proxyBeanMethods = false)
static class EndpointConfigWithShowAlways {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
Collections.emptyList(), Show.ALWAYS);
return endpoint;
}
}
@Configuration(proxyBeanMethods = false)
static class EndpointConfigWithShowNever {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
Collections.emptyList(), Show.NEVER);
return endpoint;
}
@@ -891,13 +862,9 @@ class ConfigurationPropertiesReportEndpointTests {
static class CustomSanitizingEndpointConfig {
@Bean
ConfigurationPropertiesReportEndpoint endpoint(Environment environment, SanitizingFunction sanitizingFunction) {
ConfigurationPropertiesReportEndpoint endpoint(SanitizingFunction sanitizingFunction) {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
Collections.singletonList(sanitizingFunction));
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
if (keys != null) {
endpoint.setKeysToSanitize(keys);
}
Collections.singletonList(sanitizingFunction), Show.ALWAYS);
return endpoint;
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2022 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.context.properties;
import java.security.Principal;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConfigurationPropertiesReportEndpointWebExtension}.
*
* @author Madhura Bhave
*/
class ConfigurationPropertiesReportEndpointWebExtensionTests {
private ConfigurationPropertiesReportEndpointWebExtension webExtension;
private ConfigurationPropertiesReportEndpoint delegate;
@BeforeEach
void setup() {
this.delegate = mock(ConfigurationPropertiesReportEndpoint.class);
}
@Test
void whenShowValuesIsNever() {
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.NEVER,
Collections.emptySet());
this.webExtension.configurationProperties(null);
then(this.delegate).should().getConfigurationProperties(false);
verifyPrefixed(null, false);
}
@Test
void whenShowValuesIsAlways() {
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.ALWAYS,
Collections.emptySet());
this.webExtension.configurationProperties(null);
then(this.delegate).should().getConfigurationProperties(true);
verifyPrefixed(null, true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
Collections.emptySet());
this.webExtension.configurationProperties(securityContext);
then(this.delegate).should().getConfigurationProperties(true);
verifyPrefixed(securityContext, true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
Collections.emptySet());
this.webExtension.configurationProperties(securityContext);
then(this.delegate).should().getConfigurationProperties(false);
verifyPrefixed(securityContext, false);
}
private void verifyPrefixed(SecurityContext securityContext, boolean showUnsanitized) {
given(this.delegate.getConfigurationProperties("test", showUnsanitized))
.willReturn(new ApplicationConfigurationProperties(Collections.emptyMap()));
this.webExtension.configurationPropertiesWithPrefix(securityContext, "test");
then(this.delegate).should().getConfigurationProperties("test", showUnsanitized);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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,8 +16,11 @@
package org.springframework.boot.actuate.context.properties;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -77,13 +80,13 @@ class ConfigurationPropertiesReportEndpointWebIntegrationTests {
@Bean
ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), null);
}
@Bean
ConfigurationPropertiesReportEndpointWebExtension endpointWebExtension(
ConfigurationPropertiesReportEndpoint endpoint) {
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint);
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
}
@Bean

View File

@@ -19,11 +19,8 @@ package org.springframework.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,42 +36,21 @@ import static org.assertj.core.api.Assertions.assertThat;
class SanitizerTests {
@Test
void defaultNonUriKeys() {
void whenNoSanitizationFunctionAndShowUnsanitizedIsFalse() {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somesecret", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somekey", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("token", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("secret");
assertThat(sanitizer.sanitize("sun.java.command", "--spring.data.redis.password=pa55w0rd")).isEqualTo("******");
assertThat(sanitizer.sanitize("SPRING_APPLICATION_JSON", "{password:123}")).isEqualTo("******");
assertThat(sanitizer.sanitize("spring.application.json", "{password:123}")).isEqualTo("******");
assertThat(sanitizer.sanitize("VCAP_SERVICES", "{json}")).isEqualTo("******");
assertThat(sanitizer.sanitize("vcap.services.db.codeword", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), false)).isEqualTo("******");
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), false)).isEqualTo("******");
}
@Test
void whenAdditionalKeysAreAddedValuesOfBothThemAndTheDefaultKeysAreSanitized() {
void whenNoSanitizationFunctionAndShowUnsanitizedIsTrue() {
Sanitizer sanitizer = new Sanitizer();
sanitizer.keysToSanitize("find", "confidential");
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somesecret", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somekey", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("token", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("sun.java.command", "--spring.data.redis.password=pa55w0rd")).isEqualTo("******");
assertThat(sanitizer.sanitize("confidential", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("private", "secret")).isEqualTo("secret");
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), true)).isEqualTo("secret");
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), true)).isEqualTo("something");
}
@Test
void whenCustomSanitizingFunctionPresentValueShouldBeSanitized() {
void whenCustomSanitizationFunctionAndShowUnsanitizedIsFalse() {
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
if (data.getKey().equals("custom")) {
return data.withValue("$$$$$$");
@@ -82,11 +58,27 @@ class SanitizerTests {
return data;
}));
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
assertThat(sanitizer.sanitize(secret)).isEqualTo("******");
assertThat(sanitizer.sanitize(secret, false)).isEqualTo("******");
SanitizableData custom = new SanitizableData(null, "custom", "abcde");
assertThat(sanitizer.sanitize(custom)).isEqualTo("$$$$$$");
assertThat(sanitizer.sanitize(custom, false)).isEqualTo("******");
SanitizableData hello = new SanitizableData(null, "hello", "abc");
assertThat(sanitizer.sanitize(hello)).isEqualTo("abc");
assertThat(sanitizer.sanitize(hello, false)).isEqualTo("******");
}
@Test
void whenCustomSanitizationFunctionAndShowUnsanitizedIsTrue() {
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
if (data.getKey().equals("custom")) {
return data.withValue("$$$$$$");
}
return data;
}));
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
assertThat(sanitizer.sanitize(secret, true)).isEqualTo("xyz");
SanitizableData custom = new SanitizableData(null, "custom", "abcde");
assertThat(sanitizer.sanitize(custom, true)).isEqualTo("$$$$$$");
SanitizableData hello = new SanitizableData(null, "hello", "abc");
assertThat(sanitizer.sanitize(hello, true)).isEqualTo("abc");
}
@Test
@@ -98,7 +90,7 @@ class SanitizerTests {
return data;
}));
SanitizableData password = new SanitizableData(null, "password", "123456");
assertThat(sanitizer.sanitize(password)).isEqualTo("------");
assertThat(sanitizer.sanitize(password, true)).isEqualTo("------");
}
@Test
@@ -119,101 +111,7 @@ class SanitizerTests {
});
Sanitizer sanitizer = new Sanitizer(sanitizingFunctions);
SanitizableData custom = new SanitizableData(null, sameKey, "123456");
assertThat(sanitizer.sanitize(custom)).isEqualTo("------");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleValueWithPasswordShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user:password@localhost:8080"))
.isEqualTo("http://user:******@localhost:8080");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithNonAlphaSchemeCharactersAndSingleValueWithPasswordShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "s-ch3m.+-e://user:password@localhost:8080"))
.isEqualTo("s-ch3m.+-e://user:******@localhost:8080");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleValueWithNoPasswordShouldNotBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://localhost:8080")).isEqualTo("http://localhost:8080");
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080")).isEqualTo("http://user@localhost:8080");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleValueWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user://@localhost:8080"))
.isEqualTo("http://user:******@localhost:8080");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleValuesEachWithPasswordShouldHaveAllSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(
sanitizer.sanitize(key, "http://user1:password1@localhost:8080,http://user2:password2@localhost:8082"))
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleValuesNoneWithPasswordShouldHaveNoneSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080,http://localhost:8082"))
.isEqualTo("http://user@localhost:8080,http://localhost:8082");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleValuesSomeWithPasswordShouldHaveThoseSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key,
"http://user1:password1@localhost:8080,http://user2@localhost:8082,http://localhost:8083")).isEqualTo(
"http://user1:******@localhost:8080,http://user2@localhost:8082,http://localhost:8083");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleValuesWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user1://@localhost:8080,http://user2://@localhost:8082"))
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriKeyWithUserProvidedListLiteralShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "[amqp://username:password@host/]"))
.isEqualTo("[amqp://username:******@host/]");
assertThat(sanitizer.sanitize(key,
"[http://user1:password1@localhost:8080,http://user2@localhost:8082,http://localhost:8083]")).isEqualTo(
"[http://user1:******@localhost:8080,http://user2@localhost:8082,http://localhost:8083]");
assertThat(sanitizer.sanitize(key,
"[http://user1:password1@localhost:8080,http://user2:password2@localhost:8082]"))
.isEqualTo("[http://user1:******@localhost:8080,http://user2:******@localhost:8082]");
assertThat(sanitizer.sanitize(key, "[http://user1@localhost:8080,http://user2@localhost:8082]"))
.isEqualTo("[http://user1@localhost:8080,http://user2@localhost:8082]");
}
private static Stream<String> matchingUriUserInfoKeys() {
return Stream.of("uri", "my.uri", "myuri", "uris", "my.uris", "myuris", "url", "my.url", "myurl", "urls",
"my.urls", "myurls", "address", "my.address", "myaddress", "addresses", "my.addresses", "myaddresses");
}
@Test
void regex() {
Sanitizer sanitizer = new Sanitizer(".*lock.*");
assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret");
assertThat(sanitizer.sanitize(custom, true)).isEqualTo("------");
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2012-2022 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.endpoint;
import java.security.Principal;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link Show}.
*
* @author Madhura Bhave
*/
class ShowTests {
@Test
void isShownWhenNever() {
assertThat(Show.NEVER.isShown(null, Collections.emptySet())).isFalse();
assertThat(Show.NEVER.isShown(true)).isFalse();
assertThat(Show.NEVER.isShown(false)).isFalse();
}
@Test
void isShownWhenAlways() {
assertThat(Show.ALWAYS.isShown(null, Collections.emptySet())).isTrue();
assertThat(Show.ALWAYS.isShown(true)).isTrue();
assertThat(Show.ALWAYS.isShown(true)).isTrue();
}
@Test
void isShownWithUnauthorizedResult() {
assertThat(Show.WHEN_AUTHORIZED.isShown(true)).isTrue();
assertThat(Show.WHEN_AUTHORIZED.isShown(false)).isFalse();
}
@Test
void isShownWhenUserNotInRole() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("admin")).willReturn(false);
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
}
@Test
void isShownWhenUserInRole() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("admin")).willReturn(true);
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isTrue();
}
@Test
void isShownWhenPrincipalNull() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.isUserInRole("admin")).willReturn(true);
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
}
@Test
void isShownWhenRolesEmpty() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.emptySet())).isTrue();
}
@Test
void isShownWhenSpringSecurityAuthenticationAndUnauthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
Authentication authentication = mock(Authentication.class);
given(securityContext.getPrincipal()).willReturn(authentication);
given(authentication.getAuthorities())
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
}
@Test
void isShownWhenSpringSecurityAuthenticationAndAuthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
Authentication authentication = mock(Authentication.class);
given(securityContext.getPrincipal()).willReturn(authentication);
given(authentication.getAuthorities())
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isTrue();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@@ -27,6 +27,7 @@ import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
@@ -73,7 +74,8 @@ class EnvironmentEndpointTests {
ConfigurableEnvironment environment = emptyEnvironment();
environment.getPropertySources().addLast(singleKeyPropertySource("one", "my.key", "first"));
environment.getPropertySources().addLast(singleKeyPropertySource("two", "my.key", "second"));
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
assertThat(descriptor.getActiveProfiles()).isEmpty();
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
assertThat(sources.keySet()).containsExactly("one", "two");
@@ -81,6 +83,38 @@ class EnvironmentEndpointTests {
assertThat(sources.get("two").getProperties()).containsOnlyKeys("my.key");
}
@Test
void responseWhenShowNever() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("other.service=abcde").applyTo(environment);
TestPropertyValues.of("system.service=123456").applyToSystemProperties(() -> {
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.NEVER)
.environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
.isEqualTo("******");
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
.getProperties();
assertThat(systemProperties.get("system.service").getValue()).isEqualTo("******");
return null;
});
}
@Test
void responseWhenShowWhenAuthorized() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("other.service=abcde").applyTo(environment);
TestPropertyValues.of("system.service=123456").applyToSystemProperties(() -> {
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
Show.WHEN_AUTHORIZED).environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
.isEqualTo("abcde");
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
.getProperties();
assertThat(systemProperties.get("system.service").getValue()).isEqualTo("123456");
return null;
});
}
@Test
void compositeSourceIsHandledCorrectly() {
ConfigurableEnvironment environment = emptyEnvironment();
@@ -88,82 +122,14 @@ class EnvironmentEndpointTests {
source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", "bar")));
source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", "spam")));
environment.getPropertySources().addFirst(source);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
assertThat(sources.keySet()).containsExactly("composite:one", "composite:two");
assertThat(sources.get("composite:one").getProperties().get("foo").getValue()).isEqualTo("bar");
assertThat(sources.get("composite:two").getProperties().get("foo").getValue()).isEqualTo("spam");
}
@Test
void sensitiveKeysHaveTheirValuesSanitized() {
TestPropertyValues.of("dbPassword=123456", "apiKey=123456", "mySecret=123456", "myCredentials=123456",
"VCAP_SERVICES=123456").applyToSystemProperties(() -> {
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(new StandardEnvironment())
.environment(null);
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor)
.get("systemProperties").getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
assertThat(systemProperties.get("mySecret").getValue()).isEqualTo("******");
assertThat(systemProperties.get("myCredentials").getValue()).isEqualTo("******");
assertThat(systemProperties.get("VCAP_SERVICES").getValue()).isEqualTo("******");
PropertyValueDescriptor command = systemProperties.get("sun.java.command");
if (command != null) {
assertThat(command.getValue()).isEqualTo("******");
}
return null;
});
}
@Test
void sensitiveKeysMatchingCredentialsPatternHaveTheirValuesSanitized() {
TestPropertyValues
.of("my.services.amqp-free.credentials.uri=123456", "credentials.http_api_uri=123456",
"my.services.cleardb-free.credentials=123456", "foo.mycredentials.uri=123456")
.applyToSystemProperties(() -> {
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(new StandardEnvironment())
.environment(null);
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor)
.get("systemProperties").getProperties();
assertThat(systemProperties.get("my.services.amqp-free.credentials.uri").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("credentials.http_api_uri").getValue()).isEqualTo("******");
assertThat(systemProperties.get("my.services.cleardb-free.credentials").getValue())
.isEqualTo("******");
assertThat(systemProperties.get("foo.mycredentials.uri").getValue()).isEqualTo("******");
return null;
});
}
@Test
void sensitiveKeysMatchingCustomNameHaveTheirValuesSanitized() {
TestPropertyValues.of("dbPassword=123456", "apiKey=123456").applyToSystemProperties(() -> {
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment());
endpoint.setKeysToSanitize("key");
EnvironmentDescriptor descriptor = endpoint.environment(null);
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
.getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
return null;
});
}
@Test
void sensitiveKeysMatchingCustomPatternHaveTheirValuesSanitized() {
TestPropertyValues.of("dbPassword=123456", "apiKey=123456").applyToSystemProperties(() -> {
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment());
endpoint.setKeysToSanitize(".*pass.*");
EnvironmentDescriptor descriptor = endpoint.environment(null);
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
.getProperties();
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456");
return null;
});
}
@Test
void keysMatchingCustomSanitizingFunctionHaveTheirValuesSanitized() {
ConfigurableEnvironment environment = new StandardEnvironment();
@@ -176,7 +142,7 @@ class EnvironmentEndpointTests {
return data.withValue("******");
}
return data;
})).environment(null);
}), Show.ALWAYS).environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
.isEqualTo("abcde");
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
@@ -190,7 +156,8 @@ class EnvironmentEndpointTests {
void propertyWithPlaceholderResolved() {
ConfigurableEnvironment environment = emptyEnvironment();
TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello").applyTo(environment);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue()).isEqualTo("hello");
}
@@ -198,46 +165,19 @@ class EnvironmentEndpointTests {
void propertyWithPlaceholderNotResolved() {
ConfigurableEnvironment environment = emptyEnvironment();
TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
.isEqualTo("${bar.blah}");
}
@Test
void propertyWithSensitivePlaceholderResolved() {
ConfigurableEnvironment environment = emptyEnvironment();
TestPropertyValues.of("my.foo: http://${bar.password}://hello", "bar.password: hello").applyTo(environment);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
.isEqualTo("http://******://hello");
}
@Test
void propertyWithSensitivePlaceholderNotResolved() {
ConfigurableEnvironment environment = emptyEnvironment();
TestPropertyValues.of("my.foo: http://${bar.password}://hello").applyTo(environment);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
.isEqualTo("http://${bar.password}://hello");
}
@Test
void propertyWithSensitivePlaceholderWithCustomFunctionResolved() {
ConfigurableEnvironment environment = emptyEnvironment();
TestPropertyValues.of("my.foo: http://${bar.password}://hello", "bar.password: hello").applyTo(environment);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment,
Collections.singletonList((data) -> data.withValue(data.getPropertySource().getName() + "******")))
.environment(null);
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
.isEqualTo("test******");
}
@Test
void propertyWithComplexTypeShouldNotFail() {
ConfigurableEnvironment environment = emptyEnvironment();
environment.getPropertySources()
.addFirst(singleKeyPropertySource("test", "foo", Collections.singletonMap("bar", "baz")));
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
assertThat(value).isEqualTo("Complex property type java.util.Collections$SingletonMap");
}
@@ -251,7 +191,8 @@ class EnvironmentEndpointTests {
map.put("boolean", true);
map.put("biginteger", BigInteger.valueOf(200));
environment.getPropertySources().addFirst(new MapPropertySource("test", map));
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
Map<String, PropertyValueDescriptor> properties = propertySources(descriptor).get("test").getProperties();
assertThat(properties.get("char").getValue()).isEqualTo('a');
assertThat(properties.get("integer").getValue()).isEqualTo(100);
@@ -263,26 +204,42 @@ class EnvironmentEndpointTests {
void propertyWithCharSequenceTypeIsConvertedToString() {
ConfigurableEnvironment environment = emptyEnvironment();
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", new CharSequenceProperty()));
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
assertThat(value).isEqualTo("test value");
}
@Test
void propertyEntry() {
testPropertyEntry(Show.ALWAYS, "bar", "another");
}
@Test
void propertyEntryWhenShowNever() {
testPropertyEntry(Show.NEVER, "******", "******");
}
@Test
void propertyEntryWhenShowWhenAuthorized() {
testPropertyEntry(Show.ALWAYS, "bar", "another");
}
private void testPropertyEntry(Show always, String bar, String another) {
TestPropertyValues.of("my.foo=another").applyToSystemProperties(() -> {
StandardEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment, TestPropertyValues.Type.MAP,
"test");
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("my.foo");
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
always).environmentEntry("my.foo");
assertThat(descriptor).isNotNull();
assertThat(descriptor.getProperty()).isNotNull();
assertThat(descriptor.getProperty().getSource()).isEqualTo("test");
assertThat(descriptor.getProperty().getValue()).isEqualTo("bar");
assertThat(descriptor.getProperty().getValue()).isEqualTo(bar);
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
assertThat(sources.keySet()).containsExactly("test", "systemProperties", "systemEnvironment");
assertPropertySourceEntryDescriptor(sources.get("test"), "bar", null);
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), "another", null);
assertPropertySourceEntryDescriptor(sources.get("test"), bar, null);
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), another, null);
assertPropertySourceEntryDescriptor(sources.get("systemEnvironment"), null, null);
return null;
});
@@ -294,7 +251,8 @@ class EnvironmentEndpointTests {
OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource();
propertySource.setProperty("name", "test");
environment.getPropertySources().addFirst(propertySource);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("name");
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
Show.ALWAYS).environmentEntry("name");
PropertySourceEntryDescriptor entryDescriptor = propertySources(descriptor).get("mockProperties");
assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name");
assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot");
@@ -304,7 +262,8 @@ class EnvironmentEndpointTests {
void propertyEntryNotFound() {
ConfigurableEnvironment environment = emptyEnvironment();
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", "bar"));
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("does.not.exist");
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
Show.ALWAYS).environmentEntry("does.not.exist");
assertThat(descriptor).isNotNull();
assertThat(descriptor.getProperty()).isNull();
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
@@ -317,33 +276,14 @@ class EnvironmentEndpointTests {
ConfigurableEnvironment environment = emptyEnvironment();
environment.getPropertySources().addFirst(singleKeyPropertySource("one", "a", "alpha"));
environment.getPropertySources().addFirst(singleKeyPropertySource("two", "a", "apple"));
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
.environment(null);
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
assertThat(sources.keySet()).containsExactly("two", "one");
assertThat(sources.get("one").getProperties().get("a").getValue()).isEqualTo("alpha");
assertThat(sources.get("two").getProperties().get("a").getValue()).isEqualTo("apple");
}
@Test
void uriPropertyWithSensitiveInfo() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("sensitive.uri=http://user:password@localhost:8080").applyTo(environment);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.uri");
assertThat(descriptor.getProperty().getValue()).isEqualTo("http://user:******@localhost:8080");
}
@Test
void addressesPropertyWithMultipleEntriesEachWithSensitiveInfo() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues
.of("sensitive.addresses=http://user:password@localhost:8080,http://user2:password2@localhost:8082")
.applyTo(environment);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment)
.environmentEntry("sensitive.addresses");
assertThat(descriptor.getProperty().getValue())
.isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082");
}
private static ConfigurableEnvironment emptyEnvironment() {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
@@ -418,7 +358,7 @@ class EnvironmentEndpointTests {
@Bean
EnvironmentEndpoint environmentEndpoint(Environment environment) {
return new EnvironmentEndpoint(environment);
return new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2012-2022 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.env;
import java.security.Principal;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EnvironmentEndpointWebExtension}.
*
* @author Madhura Bhave
*/
class EnvironmentEndpointWebExtensionTests {
private EnvironmentEndpointWebExtension webExtension;
private EnvironmentEndpoint delegate;
@BeforeEach
void setup() {
this.delegate = mock(EnvironmentEndpoint.class);
}
@Test
void whenShowValuesIsNever() {
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.NEVER, Collections.emptySet());
this.webExtension.environment(null, null);
then(this.delegate).should().getEnvironmentDescriptor(null, false);
verifyPrefixed(null, false);
}
@Test
void whenShowValuesIsAlways() {
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.ALWAYS, Collections.emptySet());
this.webExtension.environment(null, null);
then(this.delegate).should().getEnvironmentDescriptor(null, true);
verifyPrefixed(null, true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
Collections.emptySet());
this.webExtension.environment(securityContext, null);
then(this.delegate).should().getEnvironmentDescriptor(null, true);
verifyPrefixed(securityContext, true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() {
SecurityContext securityContext = mock(SecurityContext.class);
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
Collections.emptySet());
this.webExtension.environment(securityContext, null);
then(this.delegate).should().getEnvironmentDescriptor(null, false);
verifyPrefixed(securityContext, false);
}
private void verifyPrefixed(SecurityContext securityContext, boolean showUnsanitized) {
given(this.delegate.getEnvironmentEntryDescriptor("test", showUnsanitized))
.willReturn(new EnvironmentEntryDescriptor(null, Collections.emptyList(), Collections.emptyList()));
this.webExtension.environmentEntry(securityContext, "test");
then(this.delegate).should().getEnvironmentEntryDescriptor("test", showUnsanitized);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2022 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,11 +16,13 @@
package org.springframework.boot.actuate.env;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext;
@@ -76,17 +78,6 @@ class EnvironmentEndpointWebIntegrationTests {
.isEqualTo("${my.bar}");
}
@WebEndpointTest
void nestedPathWithSensitivePlaceholderShouldSanitize() {
Map<String, Object> map = new HashMap<>();
map.put("my.foo", "${my.password}");
map.put("my.password", "hello");
this.context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("placeholder", map));
this.client.get().uri("/actuator/env/my.foo").exchange().expectStatus().isOk().expectBody()
.jsonPath("property.value").isEqualTo("******").jsonPath(forPropertyEntry("placeholder"))
.isEqualTo("******");
}
@WebEndpointTest
void nestedPathForUnknownKeyShouldReturn404() {
this.client.get().uri("/actuator/env/this.does.not.exist").exchange().expectStatus().isNotFound();
@@ -103,16 +94,6 @@ class EnvironmentEndpointWebIntegrationTests {
.isEqualTo("${my.bar}");
}
@WebEndpointTest
void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() {
Map<String, Object> map = new HashMap<>();
map.put("my.foo", "${my.password}");
map.put("my.password", "hello");
this.context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("placeholder", map));
this.client.get().uri("/actuator/env?pattern=my.*").exchange().expectStatus().isOk().expectBody()
.jsonPath(forProperty("placeholder", "my.foo")).isEqualTo("******");
}
private String forProperty(String source, String name) {
return "propertySources[?(@.name=='" + source + "')].properties.['" + name + "'].value";
}
@@ -126,12 +107,12 @@ class EnvironmentEndpointWebIntegrationTests {
@Bean
EnvironmentEndpoint endpoint(Environment environment) {
return new EnvironmentEndpoint(environment);
return new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS);
}
@Bean
EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint endpoint) {
return new EnvironmentEndpointWebExtension(endpoint);
return new EnvironmentEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
}
}

View File

@@ -62,7 +62,6 @@ import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.spi.OperableTrigger;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobSummary;
@@ -107,7 +106,7 @@ class QuartzEndpointTests {
QuartzEndpointTests() {
this.scheduler = mock(Scheduler.class);
this.endpoint = new QuartzEndpoint(this.scheduler);
this.endpoint = new QuartzEndpoint(this.scheduler, Collections.emptyList());
}
@Test
@@ -417,7 +416,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
.willReturn(TriggerState.NORMAL);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", true);
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "3am-every-day"),
entry("description", "Sample description"), entry("type", "cron"), entry("state", TriggerState.NORMAL),
entry("priority", 3));
@@ -443,7 +442,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour", "samples")))
.willReturn(TriggerState.COMPLETE);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour", true);
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour"),
entry("description", "Every hour"), entry("type", "simple"), entry("state", TriggerState.COMPLETE),
entry("priority", 20));
@@ -470,7 +469,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour-mon-wed", "samples")))
.willReturn(TriggerState.NORMAL);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour-mon-wed");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour-mon-wed", true);
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour-mon-wed"),
entry("description", "Every working hour Mon Wed"), entry("type", "dailyTimeInterval"),
entry("state", TriggerState.NORMAL), entry("priority", 4));
@@ -499,7 +498,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("once-a-week", "samples")))
.willReturn(TriggerState.BLOCKED);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "once-a-week");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "once-a-week", true);
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "once-a-week"),
entry("description", "Once a week"), entry("type", "calendarInterval"),
entry("state", TriggerState.BLOCKED), entry("priority", 8));
@@ -524,7 +523,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("custom", "samples")))
.willReturn(TriggerState.ERROR);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "custom");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "custom", true);
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "custom"), entry("type", "custom"),
entry("state", TriggerState.ERROR), entry("priority", 9));
assertThat(triggerDetails).contains(entry("previousFireTime", previousFireTime),
@@ -542,9 +541,22 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
.willReturn(TriggerState.NORMAL);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", true);
assertThat(triggerDetails).extractingByKey("data", nestedMap()).containsOnly(entry("user", "user"),
entry("password", "******"), entry("url", "https://user:******@example.com"));
entry("password", "secret"), entry("url", "https://user:secret@example.com"));
}
@Test
void quartzTriggerWithDataMapAndShowUnsanitizedFalse() throws SchedulerException {
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("3am-every-day", "samples")
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(3, 0)).usingJobData("user", "user")
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
mockTriggers(trigger);
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
.willReturn(TriggerState.NORMAL);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", false);
assertThat(triggerDetails).extractingByKey("data", nestedMap()).containsOnly(entry("user", "******"),
entry("password", "******"), entry("url", "******"));
}
@ParameterizedTest(name = "unit {1}")
@@ -554,7 +566,7 @@ class QuartzEndpointTests {
.withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(amount, unit))
.build();
mockTriggers(trigger);
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "trigger");
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "trigger", true);
assertThat(triggerDetails).extractingByKey("calendarInterval", nestedMap())
.contains(entry("interval", expectedDuration.toMillis()));
}
@@ -575,7 +587,7 @@ class QuartzEndpointTests {
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").withDescription("A sample job")
.storeDurably().requestRecovery(false).build();
mockJobs(job);
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
assertThat(jobDetails.getGroup()).isEqualTo("samples");
assertThat(jobDetails.getName()).isEqualTo("hello");
assertThat(jobDetails.getDescription()).isEqualTo("A sample job");
@@ -600,7 +612,7 @@ class QuartzEndpointTests {
mockTriggers(trigger);
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
.willAnswer((invocation) -> Collections.singletonList(trigger));
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
assertThat(jobDetails.getTriggers()).hasSize(1);
Map<String, Object> triggerDetails = jobDetails.getTriggers().get(0);
assertThat(triggerDetails).containsOnly(entry("group", "samples"), entry("name", "3am-every-day"),
@@ -622,7 +634,7 @@ class QuartzEndpointTests {
mockTriggers(triggerOne, triggerTwo);
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
assertThat(jobDetails.getTriggers()).hasSize(2);
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
@@ -642,35 +654,30 @@ class QuartzEndpointTests {
mockTriggers(triggerOne, triggerTwo);
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
assertThat(jobDetails.getTriggers()).hasSize(2);
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
}
@Test
void quartzJobWithSensitiveDataMap() throws SchedulerException {
void quartzJobWithDataMap() throws SchedulerException {
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
mockJobs(job);
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "******"),
entry("url", "https://user:******@example.com"));
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "secret"),
entry("url", "https://user:secret@example.com"));
}
@Test
void quartzJobWithSensitiveDataMapAndCustomSanitizer() throws SchedulerException {
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("test", "value")
.usingJobData("secret", "value").build();
void quartzJobWithDataMapAndShowUnsanitizedFalse() throws SchedulerException {
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
mockJobs(job);
Sanitizer sanitizer = mock(Sanitizer.class);
given(sanitizer.sanitize("test", "value")).willReturn("value");
given(sanitizer.sanitize("secret", "value")).willReturn("----");
QuartzJobDetails jobDetails = new QuartzEndpoint(this.scheduler, sanitizer).quartzJob("samples", "hello");
assertThat(jobDetails.getData()).containsOnly(entry("test", "value"), entry("secret", "----"));
then(sanitizer).should().sanitize("test", "value");
then(sanitizer).should().sanitize("secret", "value");
then(sanitizer).shouldHaveNoMoreInteractions();
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", false);
assertThat(jobDetails.getData()).containsOnly(entry("user", "******"), entry("password", "******"),
entry("url", "******"));
}
private void mockJobs(JobDetail... jobs) throws SchedulerException {

View File

@@ -16,13 +16,18 @@
package org.springframework.boot.actuate.quartz;
import java.security.Principal;
import java.util.Collections;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzGroups;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
@@ -30,14 +35,67 @@ import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzTriggerGroup
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension.QuartzEndpointWebExtensionRuntimeHints;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link QuartzEndpointWebExtension}.
*
* @author Moritz Halbritter
* @author Madhura Bhave
*/
class QuartzEndpointWebExtensionTests {
private QuartzEndpointWebExtension webExtension;
private QuartzEndpoint delegate;
@BeforeEach
void setup() {
this.delegate = mock(QuartzEndpoint.class);
}
@Test
void whenShowValuesIsNever() throws Exception {
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.NEVER, Collections.emptySet());
this.webExtension.quartzJobOrTrigger(null, "jobs", "a", "b");
this.webExtension.quartzJobOrTrigger(null, "triggers", "a", "b");
then(this.delegate).should().quartzJob("a", "b", false);
then(this.delegate).should().quartzTrigger("a", "b", false);
}
@Test
void whenShowValuesIsAlways() throws Exception {
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.ALWAYS, Collections.emptySet());
this.webExtension.quartzJobOrTrigger(null, "a", "b", "c");
this.webExtension.quartzJobOrTrigger(null, "jobs", "a", "b");
this.webExtension.quartzJobOrTrigger(null, "triggers", "a", "b");
then(this.delegate).should().quartzJob("a", "b", true);
then(this.delegate).should().quartzTrigger("a", "b", true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() throws Exception {
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED, Collections.emptySet());
this.webExtension.quartzJobOrTrigger(securityContext, "jobs", "a", "b");
this.webExtension.quartzJobOrTrigger(securityContext, "triggers", "a", "b");
then(this.delegate).should().quartzJob("a", "b", true);
then(this.delegate).should().quartzTrigger("a", "b", true);
}
@Test
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() throws Exception {
SecurityContext securityContext = mock(SecurityContext.class);
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED, Collections.emptySet());
this.webExtension.quartzJobOrTrigger(securityContext, "jobs", "a", "b");
this.webExtension.quartzJobOrTrigger(securityContext, "triggers", "a", "b");
then(this.delegate).should().quartzJob("a", "b", false);
then(this.delegate).should().quartzTrigger("a", "b", false);
}
@Test
void shouldRegisterHints() {
RuntimeHints runtimeHints = new RuntimeHints();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@@ -42,6 +42,7 @@ import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -175,12 +176,12 @@ class QuartzEndpointWebIntegrationTests {
@Bean
QuartzEndpoint endpoint(Scheduler scheduler) {
return new QuartzEndpoint(scheduler);
return new QuartzEndpoint(scheduler, Collections.emptyList());
}
@Bean
QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
return new QuartzEndpointWebExtension(endpoint);
return new QuartzEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
}
private void mockJobs(Scheduler scheduler, JobDetail... jobs) throws SchedulerException {