Merge branch '2.2.x'
Closes gh-19060
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.health;
|
package org.springframework.boot.actuate.autoconfigure.health;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@@ -24,6 +25,9 @@ import org.springframework.boot.actuate.endpoint.SecurityContext;
|
|||||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,10 +101,34 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAuthorized(SecurityContext securityContext) {
|
private boolean isAuthorized(SecurityContext securityContext) {
|
||||||
if (securityContext.getPrincipal() == null) {
|
Principal principal = securityContext.getPrincipal();
|
||||||
|
if (principal == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return CollectionUtils.isEmpty(this.roles) || this.roles.stream().anyMatch(securityContext::isUserInRole);
|
if (CollectionUtils.isEmpty(this.roles)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
|
||||||
|
for (String role : this.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -29,9 +29,12 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Sh
|
|||||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link AutoConfiguredHealthEndpointGroup}.
|
* Tests for {@link AutoConfiguredHealthEndpointGroup}.
|
||||||
@@ -123,6 +126,30 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||||||
assertThat(group.showDetails(this.securityContext)).isFalse();
|
assertThat(group.showDetails(this.securityContext)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUserHasRightAuthorityReturnsTrue() {
|
||||||
|
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
|
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||||
|
Arrays.asList("admin", "root", "bossmode"));
|
||||||
|
Authentication principal = mock(Authentication.class);
|
||||||
|
given(principal.getAuthorities())
|
||||||
|
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
|
||||||
|
given(this.securityContext.getPrincipal()).willReturn(principal);
|
||||||
|
assertThat(group.showDetails(this.securityContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUserDoesNotHaveRightAuthoritiesReturnsFalse() {
|
||||||
|
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
|
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||||
|
Arrays.asList("admin", "rot", "bossmode"));
|
||||||
|
Authentication principal = mock(Authentication.class);
|
||||||
|
given(principal.getAuthorities())
|
||||||
|
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
|
||||||
|
given(this.securityContext.getPrincipal()).willReturn(principal);
|
||||||
|
assertThat(group.showDetails(this.securityContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void showComponentsWhenShowComponentsIsNullDelegatesToShowDetails() {
|
void showComponentsWhenShowComponentsIsNullDelegatesToShowDetails() {
|
||||||
AutoConfiguredHealthEndpointGroup alwaysGroup = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
AutoConfiguredHealthEndpointGroup alwaysGroup = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
@@ -185,6 +212,30 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||||||
assertThat(group.showComponents(this.securityContext)).isFalse();
|
assertThat(group.showComponents(this.securityContext)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUserHasRightAuthoritiesReturnsTrue() {
|
||||||
|
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
|
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||||
|
Arrays.asList("admin", "root", "bossmode"));
|
||||||
|
Authentication principal = mock(Authentication.class);
|
||||||
|
given(principal.getAuthorities())
|
||||||
|
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
|
||||||
|
given(this.securityContext.getPrincipal()).willReturn(principal);
|
||||||
|
assertThat(group.showComponents(this.securityContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUserDoesNotHaveRightAuthoritiesReturnsFalse() {
|
||||||
|
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
|
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||||
|
Arrays.asList("admin", "rot", "bossmode"));
|
||||||
|
Authentication principal = mock(Authentication.class);
|
||||||
|
given(principal.getAuthorities())
|
||||||
|
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
|
||||||
|
given(this.securityContext.getPrincipal()).willReturn(principal);
|
||||||
|
assertThat(group.showComponents(this.securityContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getStatusAggregatorReturnsStatusAggregator() {
|
void getStatusAggregatorReturnsStatusAggregator() {
|
||||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||||
|
|||||||
@@ -16,11 +16,15 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.health;
|
package org.springframework.boot.actuate.health;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,12 +112,29 @@ public class HealthWebEndpointResponseMapper {
|
|||||||
if (CollectionUtils.isEmpty(this.authorizedRoles)) {
|
if (CollectionUtils.isEmpty(this.authorizedRoles)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Principal principal = securityContext.getPrincipal();
|
||||||
|
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
|
||||||
for (String role : this.authorizedRoles) {
|
for (String role : this.authorizedRoles) {
|
||||||
if (securityContext.isUserInRole(role)) {
|
if (securityContext.isUserInRole(role)) {
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSpringSecurityAuthentication(Principal principal) {
|
||||||
|
return ClassUtils.isPresent("org.springframework.security.core.Authentication", null)
|
||||||
|
&& (principal instanceof Authentication);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import org.mockito.stubbing.Answer;
|
|||||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
@@ -85,6 +87,39 @@ class HealthWebEndpointResponseMapperTests {
|
|||||||
verify(securityContext).isUserInRole("ACTUATOR");
|
verify(securityContext).isUserInRole("ACTUATOR");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapDetailsWithRightAuthoritiesInvokesSupplier() {
|
||||||
|
HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED);
|
||||||
|
Supplier<Health> supplier = mockSupplier();
|
||||||
|
given(supplier.get()).willReturn(Health.down().build());
|
||||||
|
SecurityContext securityContext = getSecurityContext("ACTUATOR");
|
||||||
|
WebEndpointResponse<Health> response = mapper.mapDetails(supplier, securityContext);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value());
|
||||||
|
assertThat(response.getBody().getStatus()).isEqualTo(Status.DOWN);
|
||||||
|
verify(supplier).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapDetailsWithOtherAuthoritiesShouldNotInvokeSupplier() {
|
||||||
|
HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED);
|
||||||
|
Supplier<Health> supplier = mockSupplier();
|
||||||
|
given(supplier.get()).willReturn(Health.down().build());
|
||||||
|
SecurityContext securityContext = getSecurityContext("OTHER");
|
||||||
|
WebEndpointResponse<Health> response = mapper.mapDetails(supplier, securityContext);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
|
||||||
|
assertThat(response.getBody()).isNull();
|
||||||
|
verifyNoInteractions(supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityContext getSecurityContext(String other) {
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
Authentication principal = mock(Authentication.class);
|
||||||
|
given(securityContext.getPrincipal()).willReturn(principal);
|
||||||
|
given(principal.getAuthorities())
|
||||||
|
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority(other)));
|
||||||
|
return securityContext;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void mapDetailsWithUnavailableHealth() {
|
void mapDetailsWithUnavailableHealth() {
|
||||||
HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.ALWAYS);
|
HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.ALWAYS);
|
||||||
|
|||||||
Reference in New Issue
Block a user