Skip to content

Commit 3f00ba3

Browse files
committed
Polish
1 parent 200eb8f commit 3f00ba3

21 files changed

+454
-399
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* endpoints.
2525
*
2626
* @author Madhura Bhave
27+
* @since 2.0.0
2728
*/
2829
public enum AccessLevel {
2930

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryAuthorizationException.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* Authorization exceptions thrown to limit access to the endpoints.
2323
*
2424
* @author Madhura Bhave
25+
* @since 2.0.0
2526
*/
2627
public class CloudFoundryAuthorizationException extends RuntimeException {
2728

@@ -31,7 +32,8 @@ public CloudFoundryAuthorizationException(Reason reason, String message) {
3132
this(reason, message, null);
3233
}
3334

34-
public CloudFoundryAuthorizationException(Reason reason, String message, Throwable cause) {
35+
public CloudFoundryAuthorizationException(Reason reason, String message,
36+
Throwable cause) {
3537
super(message);
3638
this.reason = reason;
3739
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/SecurityResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* Response from the Cloud Foundry security interceptors.
2323
*
2424
* @author Madhura Bhave
25+
* @since 2.0.0
2526
*/
2627
public class SecurityResponse {
2728

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/Token.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* The JSON web token provided with each request that originates from Cloud Foundry.
3030
*
3131
* @author Madhura Bhave
32+
* @since 2.0.0
3233
*/
3334
public class Token {
3435

@@ -47,16 +48,14 @@ public Token(String encoded) {
4748
int firstPeriod = encoded.indexOf('.');
4849
int lastPeriod = encoded.lastIndexOf('.');
4950
if (firstPeriod <= 0 || lastPeriod <= firstPeriod) {
50-
throw new CloudFoundryAuthorizationException(
51-
Reason.INVALID_TOKEN,
51+
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
5252
"JWT must have header, body and signature");
5353
}
5454
this.header = parseJson(encoded.substring(0, firstPeriod));
5555
this.claims = parseJson(encoded.substring(firstPeriod + 1, lastPeriod));
5656
this.signature = encoded.substring(lastPeriod + 1);
5757
if (!StringUtils.hasLength(this.signature)) {
58-
throw new CloudFoundryAuthorizationException(
59-
Reason.INVALID_TOKEN,
58+
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
6059
"Token must have non-empty crypto segment");
6160
}
6261
}
@@ -67,8 +66,7 @@ private Map<String, Object> parseJson(String base64) {
6766
return JsonParserFactory.getJsonParser().parseMap(new String(bytes, UTF_8));
6867
}
6968
catch (RuntimeException ex) {
70-
throw new CloudFoundryAuthorizationException(
71-
Reason.INVALID_TOKEN,
69+
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
7270
"Token could not be parsed", ex);
7371
}
7472
}
@@ -106,13 +104,11 @@ public String getKeyId() {
106104
private <T> T getRequired(Map<String, Object> map, String key, Class<T> type) {
107105
Object value = map.get(key);
108106
if (value == null) {
109-
throw new CloudFoundryAuthorizationException(
110-
Reason.INVALID_TOKEN,
107+
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
111108
"Unable to get value from key " + key);
112109
}
113110
if (!type.isInstance(value)) {
114-
throw new CloudFoundryAuthorizationException(
115-
Reason.INVALID_TOKEN,
111+
throw new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
116112
"Unexpected value type from key " + key + " value " + value);
117113
}
118114
return (T) value;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import reactor.core.publisher.Mono;
2929

3030
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
31+
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
3132
import org.springframework.boot.actuate.endpoint.EndpointInfo;
3233
import org.springframework.boot.actuate.endpoint.OperationInvoker;
3334
import org.springframework.boot.actuate.endpoint.OperationType;
@@ -58,7 +59,8 @@
5859
*
5960
* @author Madhura Bhave
6061
*/
61-
public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping {
62+
class CloudFoundryWebFluxEndpointHandlerMapping
63+
extends AbstractWebFluxEndpointHandlerMapping {
6264

6365
private final Method handleRead = ReflectionUtils
6466
.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class);
@@ -85,38 +87,38 @@ protected void registerMappingForOperation(WebEndpointOperation operation) {
8587
if (operation.isBlocking()) {
8688
operationInvoker = new ElasticSchedulerOperationInvoker(operationInvoker);
8789
}
88-
registerMapping(createRequestMappingInfo(operation),
89-
operationType == OperationType.WRITE
90-
? new WriteOperationHandler(operationInvoker, operation.getId())
91-
: new ReadOperationHandler(operationInvoker, operation.getId()),
92-
operationType == OperationType.WRITE ? this.handleWrite
93-
: this.handleRead);
90+
Object handler = (operationType == OperationType.WRITE
91+
? new WriteOperationHandler(operationInvoker, operation.getId())
92+
: new ReadOperationHandler(operationInvoker, operation.getId()));
93+
Method method = (operationType == OperationType.WRITE ? this.handleWrite
94+
: this.handleRead);
95+
registerMapping(createRequestMappingInfo(operation), handler, method);
9496
}
9597

9698
@ResponseBody
9799
private Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
98100
ServerHttpRequest request = exchange.getRequest();
99-
return this.securityInterceptor
100-
.preHandle(exchange, "")
101-
.map(securityResponse -> {
102-
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
103-
return new ResponseEntity<>(securityResponse.getStatus());
104-
}
105-
AccessLevel accessLevel = exchange.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
106-
Map<String, Link> links = this.endpointLinksResolver.resolveLinks(getEndpoints(),
107-
request.getURI().toString());
108-
return new ResponseEntity<>(Collections.singletonMap("_links",
109-
getAccessibleLinks(accessLevel, links)), HttpStatus.OK);
110-
});
101+
return this.securityInterceptor.preHandle(exchange, "").map(securityResponse -> {
102+
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
103+
return new ResponseEntity<>(securityResponse.getStatus());
104+
}
105+
AccessLevel accessLevel = exchange
106+
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
107+
Map<String, Link> links = this.endpointLinksResolver
108+
.resolveLinks(getEndpoints(), request.getURI().toString());
109+
return new ResponseEntity<>(Collections.singletonMap("_links",
110+
getAccessibleLinks(accessLevel, links)), HttpStatus.OK);
111+
});
111112
}
112113

113-
private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel, Map<String, Link> links) {
114+
private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel,
115+
Map<String, Link> links) {
114116
if (accessLevel == null) {
115117
return new LinkedHashMap<>();
116118
}
117119
return links.entrySet().stream()
118-
.filter((e) -> e.getKey().equals("self")
119-
|| accessLevel.isAccessAllowed(e.getKey()))
120+
.filter((entry) -> entry.getKey().equals("self")
121+
|| accessLevel.isAccessAllowed(entry.getKey()))
120122
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
121123
}
122124

@@ -129,7 +131,7 @@ private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel, Map<String
129131
* @param corsConfiguration the CORS configuration for the endpoints
130132
* @param securityInterceptor the Security Interceptor
131133
*/
132-
public CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
134+
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
133135
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
134136
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
135137
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
@@ -148,31 +150,35 @@ abstract class AbstractOperationHandler {
148150

149151
private final ReactiveCloudFoundrySecurityInterceptor securityInterceptor;
150152

151-
AbstractOperationHandler(OperationInvoker operationInvoker, String endpointId, ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
153+
AbstractOperationHandler(OperationInvoker operationInvoker, String endpointId,
154+
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
152155
this.operationInvoker = operationInvoker;
153156
this.endpointId = endpointId;
154157
this.securityInterceptor = securityInterceptor;
155158
}
156159

157-
@SuppressWarnings({ "unchecked" })
158160
Publisher<ResponseEntity<Object>> doHandle(ServerWebExchange exchange,
159161
Map<String, String> body) {
160-
return this.securityInterceptor
161-
.preHandle(exchange, this.endpointId)
162-
.flatMap(securityResponse -> {
163-
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
164-
return Mono.just(new ResponseEntity<>(securityResponse.getStatus()));
165-
}
166-
Map<String, Object> arguments = new HashMap<>(exchange
167-
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
168-
if (body != null) {
169-
arguments.putAll(body);
170-
}
171-
exchange.getRequest().getQueryParams().forEach((name, values) -> arguments
172-
.put(name, values.size() == 1 ? values.get(0) : values));
173-
return handleResult((Publisher<?>) this.operationInvoker.invoke(arguments),
174-
exchange.getRequest().getMethod());
175-
});
162+
return this.securityInterceptor.preHandle(exchange, this.endpointId)
163+
.flatMap((securityResponse) -> flatMapResponse(exchange, body,
164+
securityResponse));
165+
}
166+
167+
private Mono<? extends ResponseEntity<Object>> flatMapResponse(
168+
ServerWebExchange exchange, Map<String, String> body,
169+
SecurityResponse securityResponse) {
170+
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
171+
return Mono.just(new ResponseEntity<>(securityResponse.getStatus()));
172+
}
173+
Map<String, Object> arguments = new HashMap<>(exchange
174+
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
175+
if (body != null) {
176+
arguments.putAll(body);
177+
}
178+
exchange.getRequest().getQueryParams().forEach((name, values) -> arguments
179+
.put(name, (values.size() == 1 ? values.get(0) : values)));
180+
return handleResult((Publisher<?>) this.operationInvoker.invoke(arguments),
181+
exchange.getRequest().getMethod());
176182
}
177183

178184
private Mono<ResponseEntity<Object>> handleResult(Publisher<?> result,
@@ -203,7 +209,8 @@ private ResponseEntity<Object> toResponseEntity(Object response) {
203209
final class WriteOperationHandler extends AbstractOperationHandler {
204210

205211
WriteOperationHandler(OperationInvoker operationInvoker, String endpointId) {
206-
super(operationInvoker, endpointId, CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
212+
super(operationInvoker, endpointId,
213+
CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
207214
}
208215

209216
@ResponseBody
@@ -220,7 +227,8 @@ public Publisher<ResponseEntity<Object>> handle(ServerWebExchange exchange,
220227
final class ReadOperationHandler extends AbstractOperationHandler {
221228

222229
ReadOperationHandler(OperationInvoker operationInvoker, String endpointId) {
223-
super(operationInvoker, endpointId, CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
230+
super(operationInvoker, endpointId,
231+
CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
224232
}
225233

226234
@ResponseBody

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,16 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
6969
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping(
7070
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
7171
WebClient.Builder webClientBuilder, Environment environment,
72-
DefaultCachingConfigurationFactory cachingConfigurationFactory, WebEndpointProperties webEndpointProperties) {
72+
DefaultCachingConfigurationFactory cachingConfigurationFactory,
73+
WebEndpointProperties webEndpointProperties) {
7374
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
7475
this.applicationContext, parameterMapper, cachingConfigurationFactory,
7576
endpointMediaTypes, (id) -> id);
7677
return new CloudFoundryWebFluxEndpointHandlerMapping(
7778
new EndpointMapping("/cloudfoundryapplication"),
78-
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes, getCorsConfiguration(), getSecurityInterceptor(webClientBuilder, environment));
79+
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
80+
getCorsConfiguration(),
81+
getSecurityInterceptor(webClientBuilder, environment));
7982
}
8083

8184
private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor(
@@ -91,11 +94,10 @@ private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor(
9194

9295
private ReactiveCloudFoundrySecurityService getCloudFoundrySecurityService(
9396
WebClient.Builder webClientBuilder, Environment environment) {
94-
String cloudControllerUrl = environment
95-
.getProperty("vcap.application.cf_api");
97+
String cloudControllerUrl = environment.getProperty("vcap.application.cf_api");
9698
return (cloudControllerUrl == null ? null
9799
: new ReactiveCloudFoundrySecurityService(webClientBuilder,
98-
cloudControllerUrl));
100+
cloudControllerUrl));
99101
}
100102

101103
private CorsConfiguration getCorsConfiguration() {
@@ -111,30 +113,38 @@ private CorsConfiguration getCorsConfiguration() {
111113
@Configuration
112114
@ConditionalOnClass(MatcherSecurityWebFilterChain.class)
113115
static class IgnoredPathsSecurityConfiguration {
116+
114117
@Bean
115-
public BeanPostProcessor webFilterChainPostProcessor() {
116-
return new BeanPostProcessor() {
117-
@Override
118-
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
119-
if (bean instanceof WebFilterChainProxy) {
120-
return postProcess((WebFilterChainProxy) bean);
121-
}
122-
return bean;
123-
}
124-
};
118+
public WebFilterChainPostProcessor webFilterChainPostProcessor() {
119+
return new WebFilterChainPostProcessor();
120+
}
121+
122+
}
123+
124+
private static class WebFilterChainPostProcessor implements BeanPostProcessor {
125+
126+
@Override
127+
public Object postProcessAfterInitialization(Object bean, String beanName)
128+
throws BeansException {
129+
if (bean instanceof WebFilterChainProxy) {
130+
return postProcess((WebFilterChainProxy) bean);
131+
}
132+
return bean;
125133
}
126134

127-
WebFilterChainProxy postProcess(WebFilterChainProxy existing) {
128-
ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers.pathMatchers(
129-
"/cloudfoundryapplication/**");
135+
private WebFilterChainProxy postProcess(WebFilterChainProxy existing) {
136+
ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers
137+
.pathMatchers("/cloudfoundryapplication/**");
130138
WebFilter noOpFilter = (exchange, chain) -> chain.filter(exchange);
131139
MatcherSecurityWebFilterChain ignoredRequestFilterChain = new MatcherSecurityWebFilterChain(
132140
cloudFoundryRequestMatcher, Collections.singletonList(noOpFilter));
133141
MatcherSecurityWebFilterChain allRequestsFilterChain = new MatcherSecurityWebFilterChain(
134-
ServerWebExchangeMatchers.anyExchange(), Collections.singletonList(existing));
135-
return new WebFilterChainProxy(ignoredRequestFilterChain, allRequestsFilterChain);
142+
ServerWebExchangeMatchers.anyExchange(),
143+
Collections.singletonList(existing));
144+
return new WebFilterChainProxy(ignoredRequestFilterChain,
145+
allRequestsFilterChain);
136146
}
147+
137148
}
138149

139150
}
140-

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityInterceptor.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,28 +63,31 @@ Mono<SecurityResponse> preHandle(ServerWebExchange exchange, String endpointId)
6363
}
6464
if (!StringUtils.hasText(this.applicationId)) {
6565
return Mono.error(new CloudFoundryAuthorizationException(
66-
Reason.SERVICE_UNAVAILABLE,
67-
"Application id is not available"));
66+
Reason.SERVICE_UNAVAILABLE, "Application id is not available"));
6867
}
6968
if (this.cloudFoundrySecurityService == null) {
7069
return Mono.error(new CloudFoundryAuthorizationException(
71-
Reason.SERVICE_UNAVAILABLE,
72-
"Cloud controller URL is not available"));
70+
Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available"));
7371
}
74-
return check(exchange, endpointId)
75-
.then(SUCCESS)
76-
.doOnError(throwable -> logger.error(throwable.getMessage(), throwable))
72+
return check(exchange, endpointId).then(SUCCESS).doOnError(this::logError)
7773
.onErrorResume(this::getErrorResponse);
7874
}
7975

76+
private void logError(Throwable ex) {
77+
logger.error(ex.getMessage(), ex);
78+
}
79+
8080
private Mono<Void> check(ServerWebExchange exchange, String path) {
8181
try {
8282
Token token = getToken(exchange.getRequest());
83-
return this.tokenValidator.validate(token).then(this.cloudFoundrySecurityService.getAccessLevel(token.toString(), this.applicationId))
84-
.filter(accessLevel -> accessLevel.isAccessAllowed(path))
85-
.switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
86-
"Access denied")))
87-
.doOnSuccess(accessLevel -> exchange.getAttributes().put("cloudFoundryAccessLevel", accessLevel))
83+
return this.tokenValidator.validate(token)
84+
.then(this.cloudFoundrySecurityService
85+
.getAccessLevel(token.toString(), this.applicationId))
86+
.filter((accessLevel) -> accessLevel.isAccessAllowed(path))
87+
.switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException(
88+
Reason.ACCESS_DENIED, "Access denied")))
89+
.doOnSuccess((accessLevel) -> exchange.getAttributes()
90+
.put("cloudFoundryAccessLevel", accessLevel))
8891
.then();
8992
}
9093
catch (CloudFoundryAuthorizationException ex) {
@@ -107,8 +110,7 @@ private Token getToken(ServerHttpRequest request) {
107110
String bearerPrefix = "bearer ";
108111
if (authorization == null
109112
|| !authorization.toLowerCase().startsWith(bearerPrefix)) {
110-
throw new CloudFoundryAuthorizationException(
111-
Reason.MISSING_AUTHORIZATION,
113+
throw new CloudFoundryAuthorizationException(Reason.MISSING_AUTHORIZATION,
112114
"Authorization header is missing or invalid");
113115
}
114116
return new Token(authorization.substring(bearerPrefix.length()));

0 commit comments

Comments
 (0)