Skip to content

Commit 783dcb9

Browse files
committed
Merge branch '4.0.x'
Closes gh-50018
2 parents 7edde21 + cd88bf7 commit 783dcb9

4 files changed

Lines changed: 93 additions & 18 deletions

File tree

integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,40 @@ void linksToOtherEndpointsAreProvided() {
162162
.jsonPath("_links.length()")
163163
.isEqualTo(3)
164164
.jsonPath("_links.self.href")
165-
.isNotEmpty()
165+
.value(isLinkTo("/endpoints"))
166166
.jsonPath("_links.self.templated")
167167
.isEqualTo(false)
168168
.jsonPath("_links.test.href")
169-
.isNotEmpty()
169+
.value(isLinkTo("/endpoints/test"))
170170
.jsonPath("_links.test.templated")
171171
.isEqualTo(false)
172172
.jsonPath("_links.test-part.href")
173-
.isNotEmpty()
173+
.value(isLinkTo("/endpoints/test/{part}"))
174+
.jsonPath("_links.test-part.templated")
175+
.isEqualTo(true));
176+
}
177+
178+
@Test
179+
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
180+
load(TestEndpointConfiguration.class,
181+
(client) -> client.get()
182+
.uri("?a=alpha")
183+
.exchange()
184+
.expectStatus()
185+
.isOk()
186+
.expectBody()
187+
.jsonPath("_links.length()")
188+
.isEqualTo(3)
189+
.jsonPath("_links.self.href")
190+
.value(isLinkTo("/endpoints"))
191+
.jsonPath("_links.self.templated")
192+
.isEqualTo(false)
193+
.jsonPath("_links.test.href")
194+
.value(isLinkTo("/endpoints/test"))
195+
.jsonPath("_links.test.templated")
196+
.isEqualTo(false)
197+
.jsonPath("_links.test-part.href")
198+
.value(isLinkTo("/endpoints/test/{part}"))
174199
.jsonPath("_links.test-part.templated")
175200
.isEqualTo(true));
176201
}
@@ -668,6 +693,10 @@ void endpointCanProduceAResponseWithACustomStatus() {
668693
(client) -> client.get().uri("/customstatus").exchange().expectStatus().isEqualTo(234));
669694
}
670695

696+
private Consumer<Object> isLinkTo(String target) {
697+
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
698+
}
699+
671700
protected abstract int getPort(T context);
672701

673702
protected void validateErrorBody(WebTestClient.BodyContentSpec body, HttpStatus status, String path,

module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.web.cors.CorsConfiguration;
5151
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
5252
import org.springframework.web.server.ServerWebExchange;
53+
import org.springframework.web.util.UriComponentsBuilder;
5354

5455
/**
5556
* A custom {@link RequestMappingInfoHandlerMapping} that makes web endpoints available on
@@ -111,8 +112,9 @@ public Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
111112
return new ResponseEntity<>(securityResponse.getStatus());
112113
}
113114
AccessLevel accessLevel = exchange.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
115+
String requestUri = UriComponentsBuilder.fromUri(request.getURI()).replaceQuery(null).toUriString();
114116
Map<String, Link> links = CloudFoundryWebFluxEndpointHandlerMapping.this.linksResolver
115-
.resolveLinks(request.getURI().toString());
117+
.resolveLinks(requestUri);
116118
return new ResponseEntity<>(
117119
Collections.singletonMap("_links", getAccessibleLinks(accessLevel, links)), HttpStatus.OK);
118120
});

module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,23 +147,23 @@ void linksToOtherEndpointsWithFullAccess() {
147147
.jsonPath("_links.length()")
148148
.isEqualTo(5)
149149
.jsonPath("_links.self.href")
150-
.isNotEmpty()
150+
.value(isLinkTo("/cfApplication"))
151151
.jsonPath("_links.self.templated")
152152
.isEqualTo(false)
153153
.jsonPath("_links.info.href")
154-
.isNotEmpty()
154+
.value(isLinkTo("/cfApplication/info"))
155155
.jsonPath("_links.info.templated")
156156
.isEqualTo(false)
157157
.jsonPath("_links.env.href")
158-
.isNotEmpty()
158+
.value(isLinkTo("/cfApplication/env"))
159159
.jsonPath("_links.env.templated")
160160
.isEqualTo(false)
161161
.jsonPath("_links.test.href")
162-
.isNotEmpty()
162+
.value(isLinkTo("/cfApplication/test"))
163163
.jsonPath("_links.test.templated")
164164
.isEqualTo(false)
165165
.jsonPath("_links.test-part.href")
166-
.isNotEmpty()
166+
.value(isLinkTo("/cfApplication/test/{part}"))
167167
.jsonPath("_links.test-part.templated")
168168
.isEqualTo(true)));
169169
}
@@ -197,11 +197,11 @@ void linksToOtherEndpointsWithRestrictedAccess() {
197197
.jsonPath("_links.length()")
198198
.isEqualTo(2)
199199
.jsonPath("_links.self.href")
200-
.isNotEmpty()
200+
.value(isLinkTo("/cfApplication"))
201201
.jsonPath("_links.self.templated")
202202
.isEqualTo(false)
203203
.jsonPath("_links.info.href")
204-
.isNotEmpty()
204+
.value(isLinkTo("/cfApplication/info"))
205205
.jsonPath("_links.info.templated")
206206
.isEqualTo(false)
207207
.jsonPath("_links.env")
@@ -212,12 +212,34 @@ void linksToOtherEndpointsWithRestrictedAccess() {
212212
.doesNotExist()));
213213
}
214214

215+
@Test
216+
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
217+
given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
218+
given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED));
219+
this.contextRunner.run(withWebTestClient((client) -> client.get()
220+
.uri("/cfApplication?x=1")
221+
.accept(MediaType.APPLICATION_JSON)
222+
.header("Authorization", "bearer " + mockAccessToken())
223+
.exchange()
224+
.expectStatus()
225+
.isOk()
226+
.expectBody()
227+
.jsonPath("_links.self.href")
228+
.value(isLinkTo("/cfApplication"))
229+
.jsonPath("_links.info.href")
230+
.value(isLinkTo("/cfApplication/info"))));
231+
}
232+
215233
@Test
216234
void unknownEndpointsAreForbidden() {
217235
this.contextRunner.run(withWebTestClient(
218236
(client) -> client.get().uri("/cfApplication/unknown").exchange().expectStatus().isForbidden()));
219237
}
220238

239+
private Consumer<Object> isLinkTo(String target) {
240+
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
241+
}
242+
221243
private ContextConsumer<AssertableReactiveWebApplicationContext> withWebTestClient(
222244
Consumer<WebTestClient> clientConsumer) {
223245
return (context) -> {

module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,23 @@ void linksToOtherEndpointsWithFullAccess() {
135135
.jsonPath("_links.length()")
136136
.isEqualTo(5)
137137
.jsonPath("_links.self.href")
138-
.isNotEmpty()
138+
.value(isLinkTo("/cfApplication"))
139139
.jsonPath("_links.self.templated")
140140
.isEqualTo(false)
141141
.jsonPath("_links.info.href")
142-
.isNotEmpty()
142+
.value(isLinkTo("/cfApplication/info"))
143143
.jsonPath("_links.info.templated")
144144
.isEqualTo(false)
145145
.jsonPath("_links.env.href")
146-
.isNotEmpty()
146+
.value(isLinkTo("/cfApplication/env"))
147147
.jsonPath("_links.env.templated")
148148
.isEqualTo(false)
149149
.jsonPath("_links.test.href")
150-
.isNotEmpty()
150+
.value(isLinkTo("/cfApplication/test"))
151151
.jsonPath("_links.test.templated")
152152
.isEqualTo(false)
153153
.jsonPath("_links.test-part.href")
154-
.isNotEmpty()
154+
.value(isLinkTo("/cfApplication/test/{part}"))
155155
.jsonPath("_links.test-part.templated")
156156
.isEqualTo(true));
157157
}
@@ -186,11 +186,11 @@ void linksToOtherEndpointsWithRestrictedAccess() {
186186
.jsonPath("_links.length()")
187187
.isEqualTo(2)
188188
.jsonPath("_links.self.href")
189-
.isNotEmpty()
189+
.value(isLinkTo("/cfApplication"))
190190
.jsonPath("_links.self.templated")
191191
.isEqualTo(false)
192192
.jsonPath("_links.info.href")
193-
.isNotEmpty()
193+
.value(isLinkTo("/cfApplication/info"))
194194
.jsonPath("_links.info.templated")
195195
.isEqualTo(false)
196196
.jsonPath("_links.env")
@@ -201,6 +201,24 @@ void linksToOtherEndpointsWithRestrictedAccess() {
201201
.doesNotExist());
202202
}
203203

204+
@Test
205+
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
206+
given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED);
207+
load(TestEndpointConfiguration.class,
208+
(client) -> client.get()
209+
.uri("/cfApplication?x=1")
210+
.accept(MediaType.APPLICATION_JSON)
211+
.header("Authorization", "bearer " + mockAccessToken())
212+
.exchange()
213+
.expectStatus()
214+
.isOk()
215+
.expectBody()
216+
.jsonPath("_links.self.href")
217+
.value(isLinkTo("/cfApplication"))
218+
.jsonPath("_links.info.href")
219+
.value(isLinkTo("/cfApplication/info")));
220+
}
221+
204222
@Test
205223
void unknownEndpointsAreForbidden() {
206224
load(TestEndpointConfiguration.class,
@@ -212,6 +230,10 @@ void unknownEndpointsAreForbidden() {
212230
.isForbidden());
213231
}
214232

233+
private Consumer<Object> isLinkTo(String target) {
234+
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
235+
}
236+
215237
private void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
216238
BiConsumer<ApplicationContext, WebTestClient> consumer = (context, client) -> clientConsumer.accept(client);
217239
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)

0 commit comments

Comments
 (0)