Skip to content

Commit 95c08d3

Browse files
spericasjansupol
authored andcommitted
Support for new property to ignore responses in exceptions thrown by the Client API. If the property jersey.config.client.ignoreExceptionResponse is set to true, any response in an exception thrown by the Client API will be mapped to an empty response that only includes the status code of the original one. This is to prevent accidental leaks of confidential data.
Signed-off-by: Santiago Pericasgeertsen <[email protected]>
1 parent 684bbf8 commit 95c08d3

File tree

3 files changed

+180
-16
lines changed

3 files changed

+180
-16
lines changed

core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,27 @@ public final class ClientProperties {
167167
*/
168168
public static final String USE_ENCODING = "jersey.config.client.useEncoding";
169169

170+
/**
171+
* Ignore a response in an exception thrown by the client API by not forwarding
172+
* it to this service's client. A value of {@code true} indicates that responses
173+
* will be ignored, and only the response status will return to the client. This
174+
* property will normally be specified as a system property; note that system
175+
* properties are only visible if {@link CommonProperties#ALLOW_SYSTEM_PROPERTIES_PROVIDER}
176+
* is set to {@code true}.
177+
* <p>
178+
* The value MUST be an instance convertible to {@link java.lang.Boolean}.
179+
* </p>
180+
* <p>
181+
* The default value is {@code false}.
182+
* </p>
183+
* <p>
184+
* The name of the configuration property is <tt>{@value}</tt>.
185+
* </p>
186+
*
187+
* @see org.glassfish.jersey.CommonProperties#ALLOW_SYSTEM_PROPERTIES_PROVIDER
188+
*/
189+
public static final String IGNORE_EXCEPTION_RESPONSE = "jersey.config.client.ignoreExceptionResponse";
190+
170191
/**
171192
* If {@code true} then disable auto-discovery on the client.
172193
* <p>

core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public class JerseyInvocation implements javax.ws.rs.client.Invocation {
8282
// Copy request context when invoke or submit methods are invoked.
8383
private final boolean copyRequestContext;
8484

85+
private boolean ignoreResponseException;
86+
8587
private JerseyInvocation(final Builder builder) {
8688
this(builder, false);
8789
}
@@ -91,6 +93,15 @@ private JerseyInvocation(final Builder builder, final boolean copyRequestContext
9193

9294
this.requestContext = new ClientRequest(builder.requestContext);
9395
this.copyRequestContext = copyRequestContext;
96+
97+
Object value = builder.requestContext.getConfiguration()
98+
.getProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE);
99+
if (value != null) {
100+
Boolean booleanValue = PropertiesHelper.convertValue(value, Boolean.class);
101+
if (booleanValue != null) {
102+
this.ignoreResponseException = booleanValue;
103+
}
104+
}
94105
}
95106

96107
private enum EntityPresence {
@@ -875,56 +886,60 @@ public JerseyInvocation property(final String name, final Object value) {
875886
}
876887

877888
private ProcessingException convertToException(final Response response) {
889+
// Use an empty response if ignoring response in exception
890+
final int statusCode = response.getStatus();
891+
final Response finalResponse = ignoreResponseException ? Response.status(statusCode).build() : response;
892+
878893
try {
879894
// Buffer and close entity input stream (if any) to prevent
880895
// leaking connections (see JERSEY-2157).
881896
response.bufferEntity();
882897

883898
final WebApplicationException webAppException;
884-
final int statusCode = response.getStatus();
885899
final Response.Status status = Response.Status.fromStatusCode(statusCode);
886900

887901
if (status == null) {
888-
final Response.Status.Family statusFamily = response.getStatusInfo().getFamily();
889-
webAppException = createExceptionForFamily(response, statusFamily);
902+
final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily();
903+
webAppException = createExceptionForFamily(finalResponse, statusFamily);
890904
} else {
891905
switch (status) {
892906
case BAD_REQUEST:
893-
webAppException = new BadRequestException(response);
907+
webAppException = new BadRequestException(finalResponse);
894908
break;
895909
case UNAUTHORIZED:
896-
webAppException = new NotAuthorizedException(response);
910+
webAppException = new NotAuthorizedException(finalResponse);
897911
break;
898912
case FORBIDDEN:
899-
webAppException = new ForbiddenException(response);
913+
webAppException = new ForbiddenException(finalResponse);
900914
break;
901915
case NOT_FOUND:
902-
webAppException = new NotFoundException(response);
916+
webAppException = new NotFoundException(finalResponse);
903917
break;
904918
case METHOD_NOT_ALLOWED:
905-
webAppException = new NotAllowedException(response);
919+
webAppException = new NotAllowedException(finalResponse);
906920
break;
907921
case NOT_ACCEPTABLE:
908-
webAppException = new NotAcceptableException(response);
922+
webAppException = new NotAcceptableException(finalResponse);
909923
break;
910924
case UNSUPPORTED_MEDIA_TYPE:
911-
webAppException = new NotSupportedException(response);
925+
webAppException = new NotSupportedException(finalResponse);
912926
break;
913927
case INTERNAL_SERVER_ERROR:
914-
webAppException = new InternalServerErrorException(response);
928+
webAppException = new InternalServerErrorException(finalResponse);
915929
break;
916930
case SERVICE_UNAVAILABLE:
917-
webAppException = new ServiceUnavailableException(response);
931+
webAppException = new ServiceUnavailableException(finalResponse);
918932
break;
919933
default:
920-
final Response.Status.Family statusFamily = response.getStatusInfo().getFamily();
921-
webAppException = createExceptionForFamily(response, statusFamily);
934+
final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily();
935+
webAppException = createExceptionForFamily(finalResponse, statusFamily);
922936
}
923937
}
924938

925-
return new ResponseProcessingException(response, webAppException);
939+
return new ResponseProcessingException(finalResponse, webAppException);
926940
} catch (final Throwable t) {
927-
return new ResponseProcessingException(response, LocalizationMessages.RESPONSE_TO_EXCEPTION_CONVERSION_FAILED(), t);
941+
return new ResponseProcessingException(finalResponse,
942+
LocalizationMessages.RESPONSE_TO_EXCEPTION_CONVERSION_FAILED(), t);
928943
}
929944
}
930945

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.tests.e2e.client;
18+
19+
import javax.ws.rs.GET;
20+
import javax.ws.rs.Path;
21+
import javax.ws.rs.WebApplicationException;
22+
import javax.ws.rs.client.Client;
23+
import javax.ws.rs.client.ClientBuilder;
24+
import javax.ws.rs.core.Application;
25+
import javax.ws.rs.core.NewCookie;
26+
import javax.ws.rs.core.Response;
27+
import java.net.URI;
28+
import java.util.concurrent.atomic.AtomicReference;
29+
30+
import static junit.framework.TestCase.assertEquals;
31+
import static junit.framework.TestCase.assertFalse;
32+
import static junit.framework.TestCase.assertNull;
33+
import org.glassfish.jersey.CommonProperties;
34+
import org.glassfish.jersey.client.ClientProperties;
35+
import org.glassfish.jersey.server.ResourceConfig;
36+
import org.glassfish.jersey.test.JerseyTest;
37+
import org.junit.AfterClass;
38+
import org.junit.BeforeClass;
39+
import org.junit.Test;
40+
41+
/**
42+
* Tests ignoring of client responses in exceptions.
43+
*
44+
* @author Santiago Pericas-Geertsen
45+
*/
46+
public class IgnoreExceptionResponseTest extends JerseyTest {
47+
48+
static String lastAllowSystemProperties;
49+
static String lastIgnoreExceptionResponse;
50+
static AtomicReference<URI> baseUri = new AtomicReference<>();
51+
52+
@Override
53+
protected Application configure() {
54+
return new ResourceConfig(TestResource.class);
55+
}
56+
57+
public IgnoreExceptionResponseTest() {
58+
baseUri.set(getBaseUri());
59+
}
60+
61+
/**
62+
* Sets ignore exception response as system property after enabling the provider.
63+
*/
64+
@BeforeClass
65+
public static void startUp() {
66+
lastAllowSystemProperties = System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, "true");
67+
lastIgnoreExceptionResponse = System.setProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE, "true");
68+
}
69+
70+
/**
71+
* Restores state after completion.
72+
*/
73+
@AfterClass
74+
public static void cleanUp() {
75+
if (lastIgnoreExceptionResponse != null) {
76+
System.setProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE, lastIgnoreExceptionResponse);
77+
}
78+
if (lastAllowSystemProperties != null) {
79+
System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, lastAllowSystemProperties);
80+
}
81+
}
82+
83+
@Test
84+
public void test() {
85+
Client client = ClientBuilder.newClient();
86+
Response r = client.target(getBaseUri())
87+
.path("test")
88+
.path("first")
89+
.request()
90+
.get();
91+
assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), r.getStatus());
92+
assertNull(r.getHeaderString("confidential"));
93+
assertNull(r.getCookies().get("confidential"));
94+
assertFalse(r.hasEntity());
95+
}
96+
97+
@Path("test")
98+
public static class TestResource {
99+
100+
@Path("first")
101+
@GET
102+
public String first() {
103+
Client client = ClientBuilder.newClient();
104+
String entity = client.target(baseUri.get())
105+
.path("test")
106+
.path("second")
107+
.request()
108+
.get(String.class); // WebApplicationException may be thrown
109+
return processEntity(entity);
110+
}
111+
112+
@Path("second")
113+
@GET
114+
public String second() {
115+
throw new WebApplicationException(
116+
"Leaking confidential information",
117+
Response.status(500)
118+
.header("confidential", "nuke-codes")
119+
.cookie(NewCookie.valueOf("confidential=more-nuke-codes"))
120+
.entity("even-more-nuke-codes")
121+
.build());
122+
}
123+
124+
private String processEntity(String entity) {
125+
return entity; // filter confidential information
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)