Skip to content

Commit 0c9c5b2

Browse files
authored
Merge pull request #201 from IABTechLab/tjm-UID2-2771-return-optout-url-from-core
Use the returned OptOut URL from the attestation request
2 parents 1779bb2 + b58a9cf commit 0c9c5b2

11 files changed

+252
-130
lines changed

pom.xml

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.uid2</groupId>
88
<artifactId>uid2-shared</artifactId>
9-
<version>7.2.0-41efc58fbf</version>
9+
<version>7.2.4-SNAPSHOT</version>
1010
<name>${project.groupId}:${project.artifactId}</name>
1111
<description>Library for all the shared uid2 operations</description>
1212
<url>https://github.com/IABTechLab/uid2docs</url>
@@ -209,6 +209,11 @@
209209
<groupId>software.amazon.awssdk</groupId>
210210
<artifactId>kms</artifactId>
211211
</dependency>
212+
<dependency>
213+
<groupId>javax.xml.bind</groupId>
214+
<artifactId>jaxb-api</artifactId>
215+
<version>2.3.1</version>
216+
</dependency>
212217
<dependency>
213218
<groupId>junit</groupId>
214219
<artifactId>junit</artifactId>
@@ -266,7 +271,7 @@
266271
<dependency>
267272
<groupId>org.assertj</groupId>
268273
<artifactId>assertj-core</artifactId>
269-
<version>3.23.1</version>
274+
<version>3.25.2</version>
270275
<scope>test</scope>
271276
</dependency>
272277
</dependencies>

src/main/java/com/uid2/shared/attest/AttestationTokenRetriever.java renamed to src/main/java/com/uid2/shared/attest/AttestationResponseHandler.java

+48-30
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import java.util.concurrent.locks.Lock;
2525
import java.util.concurrent.locks.ReentrantLock;
2626

27-
public class AttestationTokenRetriever {
28-
private static final Logger LOGGER = LoggerFactory.getLogger(AttestationTokenRetriever.class);
27+
public class AttestationResponseHandler {
28+
private static final Logger LOGGER = LoggerFactory.getLogger(AttestationResponseHandler.class);
2929

3030
private final IAttestationProvider attestationProvider;
3131
private final String clientApiToken;
@@ -47,27 +47,28 @@ public class AttestationTokenRetriever {
4747
private final AttestationTokenDecryptor attestationTokenDecryptor;
4848
private final String appVersionHeader;
4949
private final int attestCheckMilliseconds;
50-
51-
public AttestationTokenRetriever(Vertx vertx,
52-
String attestationEndpoint,
53-
String clientApiToken,
54-
ApplicationVersion appVersion,
55-
IAttestationProvider attestationProvider,
56-
Handler<Pair<Integer, String>> responseWatcher,
57-
Proxy proxy) {
50+
private final AtomicReference<String> optOutUrl;
51+
52+
public AttestationResponseHandler(Vertx vertx,
53+
String attestationEndpoint,
54+
String clientApiToken,
55+
ApplicationVersion appVersion,
56+
IAttestationProvider attestationProvider,
57+
Handler<Pair<Integer, String>> responseWatcher,
58+
Proxy proxy) {
5859
this(vertx, attestationEndpoint, clientApiToken, appVersion, attestationProvider, responseWatcher, proxy, new InstantClock(), null, null, 60000);
5960
}
60-
public AttestationTokenRetriever(Vertx vertx,
61-
String attestationEndpoint,
62-
String clientApiToken,
63-
ApplicationVersion appVersion,
64-
IAttestationProvider attestationProvider,
65-
Handler<Pair<Integer, String>> responseWatcher,
66-
Proxy proxy,
67-
IClock clock,
68-
URLConnectionHttpClient httpClient,
69-
AttestationTokenDecryptor attestationTokenDecryptor,
70-
int attestCheckMilliseconds) {
61+
public AttestationResponseHandler(Vertx vertx,
62+
String attestationEndpoint,
63+
String clientApiToken,
64+
ApplicationVersion appVersion,
65+
IAttestationProvider attestationProvider,
66+
Handler<Pair<Integer, String>> responseWatcher,
67+
Proxy proxy,
68+
IClock clock,
69+
URLConnectionHttpClient httpClient,
70+
AttestationTokenDecryptor attestationTokenDecryptor,
71+
int attestCheckMilliseconds) {
7172
this.vertx = vertx;
7273
this.attestationEndpoint = attestationEndpoint;
7374
this.encodedAttestationEndpoint = this.encodeStringUnicodeAttestationEndpoint(attestationEndpoint);
@@ -77,6 +78,7 @@ public AttestationTokenRetriever(Vertx vertx,
7778
this.attestationToken = new AtomicReference<>(null);
7879
this.optOutJwt = new AtomicReference<>(null);
7980
this.coreJwt = new AtomicReference<>(null);
81+
this.optOutUrl = new AtomicReference<>(null);
8082
this.responseWatcher = responseWatcher;
8183
this.clock = clock;
8284
this.lock = new ReentrantLock();
@@ -125,7 +127,7 @@ private void attestationExpirationCheck(long timerId) {
125127
}
126128

127129
attest();
128-
} catch (AttestationTokenRetrieverException e) {
130+
} catch (AttestationResponseHandlerException e) {
129131
notifyResponseWatcher(401, e.getMessage());
130132
LOGGER.info("Re-attest failed: ", e);
131133
} catch (IOException e){
@@ -144,9 +146,9 @@ private void scheduleAttestationExpirationCheck() {
144146
}
145147
}
146148

147-
public void attest() throws IOException, AttestationTokenRetrieverException {
149+
public void attest() throws IOException, AttestationResponseHandlerException {
148150
if (!attestationProvider.isReady()) {
149-
throw new AttestationTokenRetrieverException("attestation provider is not ready");
151+
throw new AttestationResponseHandlerException("attestation provider is not ready");
150152
}
151153

152154
try {
@@ -177,26 +179,26 @@ public void attest() throws IOException, AttestationTokenRetrieverException {
177179

178180
if (statusCode < 200 || statusCode >= 300) {
179181
LOGGER.warn("attestation failed with UID2 Core returning statusCode=" + statusCode);
180-
throw new AttestationTokenRetrieverException(statusCode, "unexpected status code from uid core service");
182+
throw new AttestationResponseHandlerException(statusCode, "unexpected status code from uid core service");
181183
}
182184

183185
JsonObject responseJson = (JsonObject) Json.decodeValue(responseBody);
184186
if (isFailed(responseJson)) {
185-
throw new AttestationTokenRetrieverException(statusCode, "response did not return a successful status");
187+
throw new AttestationResponseHandlerException(statusCode, "response did not return a successful status");
186188
}
187189

188190
JsonObject innerBody = responseJson.getJsonObject("body");
189191
if (innerBody == null) {
190-
throw new AttestationTokenRetrieverException(statusCode, "response did not contain a body object");
192+
throw new AttestationResponseHandlerException(statusCode, "response did not contain a body object");
191193
}
192194

193195
String atoken = getAttestationToken(innerBody);
194196
if (atoken == null) {
195-
throw new AttestationTokenRetrieverException(statusCode, "response json does not contain body.attestation_token");
197+
throw new AttestationResponseHandlerException(statusCode, "response json does not contain body.attestation_token");
196198
}
197199
String expiresAt = getAttestationTokenExpiresAt(innerBody);
198200
if (expiresAt == null) {
199-
throw new AttestationTokenRetrieverException(statusCode, "response json does not contain body.expiresAt");
201+
throw new AttestationResponseHandlerException(statusCode, "response json does not contain body.expiresAt");
200202
}
201203

202204
atoken = new String(attestationTokenDecryptor.decrypt(Base64.getDecoder().decode(atoken), keyPair.getPrivate()), StandardCharsets.UTF_8);
@@ -205,12 +207,13 @@ public void attest() throws IOException, AttestationTokenRetrieverException {
205207
setAttestationTokenExpiresAt(expiresAt);
206208
setOptoutJWTFromResponse(innerBody);
207209
setCoreJWTFromResponse(innerBody);
210+
setOptoutURLFromResponse(innerBody);
208211

209212
scheduleAttestationExpirationCheck();
210213
} catch (IOException ioe) {
211214
throw ioe;
212215
} catch (Exception e) {
213-
throw new AttestationTokenRetrieverException(e);
216+
throw new AttestationResponseHandlerException(e);
214217
}
215218
}
216219

@@ -230,6 +233,10 @@ public String getCoreJWT() {
230233
return this.coreJwt.get();
231234
}
232235

236+
public String getOptOutUrl() {
237+
return this.optOutUrl.get();
238+
}
239+
233240
public String getAppVersionHeader() {
234241
return this.appVersionHeader;
235242
}
@@ -266,6 +273,17 @@ private void setCoreJWTFromResponse(JsonObject responseBody) {
266273
}
267274
}
268275

276+
private void setOptoutURLFromResponse(JsonObject responseBody) {
277+
String url = responseBody.getString("optout_url");
278+
if (url == null) {
279+
LOGGER.info("OptOut URL not received");
280+
} else {
281+
LOGGER.info("OptOut URL received");
282+
LOGGER.debug("OptOut URL to use: {}", url);
283+
this.optOutUrl.set(url);
284+
}
285+
}
286+
269287
private static boolean isFailed(JsonObject responseJson) {
270288
return responseJson.getString("status") == null || !responseJson.getString("status").equals("success");
271289
}

src/main/java/com/uid2/shared/attest/AttestationTokenRetrieverException.java renamed to src/main/java/com/uid2/shared/attest/AttestationResponseHandlerException.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package com.uid2.shared.attest;
22

3-
public class AttestationTokenRetrieverException extends Exception {
3+
public class AttestationResponseHandlerException extends Exception {
44
private int statusCode = 0;
55

6-
public AttestationTokenRetrieverException(Throwable t) {
6+
public AttestationResponseHandlerException(Throwable t) {
77
super(t);
88
}
99

10-
public AttestationTokenRetrieverException(String message) {
10+
public AttestationResponseHandlerException(String message) {
1111
super(message);
1212
}
1313

14-
public AttestationTokenRetrieverException(int statusCode, String message) {
14+
public AttestationResponseHandlerException(int statusCode, String message) {
1515
super("http status: " + String.valueOf(statusCode) + ", " + message);
1616
this.statusCode = statusCode;
1717
}

src/main/java/com/uid2/shared/attest/UidCoreClient.java

+18-18
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ public class UidCoreClient implements IUidCoreClient, DownloadCloudStorage {
2121
private String userToken;
2222
private final String appVersionHeader;
2323
private boolean allowContentFromLocalFileSystem = false;
24-
private final AttestationTokenRetriever attestationTokenRetriever;
24+
private final AttestationResponseHandler attestationResponseHandler;
2525

2626

27-
public static UidCoreClient createNoAttest(String userToken, AttestationTokenRetriever attestationTokenRetriever) {
28-
return new UidCoreClient(userToken, CloudUtils.defaultProxy, attestationTokenRetriever, null);
27+
public static UidCoreClient createNoAttest(String userToken, AttestationResponseHandler attestationResponseHandler) {
28+
return new UidCoreClient(userToken, CloudUtils.defaultProxy, attestationResponseHandler, null);
2929
}
3030

3131
public UidCoreClient(String userToken,
3232
Proxy proxy,
33-
AttestationTokenRetriever attestationTokenRetriever) {
34-
this(userToken, proxy, attestationTokenRetriever, null);
33+
AttestationResponseHandler attestationResponseHandler) {
34+
this(userToken, proxy, attestationResponseHandler, null);
3535
}
3636

3737
public UidCoreClient(String userToken,
3838
Proxy proxy,
39-
AttestationTokenRetriever attestationTokenRetriever,
39+
AttestationResponseHandler attestationResponseHandler,
4040
URLConnectionHttpClient httpClient) {
4141
this.proxy = proxy;
4242
this.userToken = userToken;
@@ -46,13 +46,13 @@ public UidCoreClient(String userToken,
4646
} else {
4747
this.httpClient = httpClient;
4848
}
49-
if (attestationTokenRetriever == null) {
50-
throw new IllegalArgumentException("attestationTokenRetriever can not be null");
49+
if (attestationResponseHandler == null) {
50+
throw new IllegalArgumentException("attestationResponseHandler can not be null");
5151
} else {
52-
this.attestationTokenRetriever = attestationTokenRetriever;
52+
this.attestationResponseHandler = attestationResponseHandler;
5353
}
5454

55-
this.appVersionHeader = attestationTokenRetriever.getAppVersionHeader();
55+
this.appVersionHeader = attestationResponseHandler.getAppVersionHeader();
5656
}
5757

5858
@Override
@@ -89,21 +89,21 @@ private InputStream readContentFromLocalFileSystem(String path, Proxy proxy) thr
8989
return (proxy == null ? new URL(path).openConnection() : new URL(path).openConnection(proxy)).getInputStream();
9090
}
9191

92-
private InputStream getWithAttest(String path) throws IOException, AttestationTokenRetrieverException {
93-
if (!attestationTokenRetriever.attested()) {
94-
attestationTokenRetriever.attest();
92+
private InputStream getWithAttest(String path) throws IOException, AttestationResponseHandlerException {
93+
if (!attestationResponseHandler.attested()) {
94+
attestationResponseHandler.attest();
9595
}
9696

97-
String attestationToken = attestationTokenRetriever.getAttestationToken();
97+
String attestationToken = attestationResponseHandler.getAttestationToken();
9898

9999
HttpResponse<String> httpResponse;
100100
httpResponse = sendHttpRequest(path, attestationToken);
101101

102102
// This should never happen, but keeping this part of the code just to be extra safe.
103103
if (httpResponse.statusCode() == 401) {
104104
LOGGER.info("Initial response from UID2 Core returned 401, performing attestation");
105-
attestationTokenRetriever.attest();
106-
attestationToken = attestationTokenRetriever.getAttestationToken();
105+
attestationResponseHandler.attest();
106+
attestationToken = attestationResponseHandler.getAttestationToken();
107107
httpResponse = sendHttpRequest(path, attestationToken);
108108
}
109109

@@ -139,8 +139,8 @@ private HttpResponse<String> sendHttpRequest(String path, String attestationToke
139139
return httpResponse;
140140
}
141141

142-
protected AttestationTokenRetriever getAttestationTokenRetriever() {
143-
return attestationTokenRetriever;
142+
protected AttestationResponseHandler getAttestationTokenRetriever() {
143+
return attestationResponseHandler;
144144
}
145145

146146
public void setUserToken(String userToken) {
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,60 @@
11
package com.uid2.shared.attest;
22

3-
import com.uid2.shared.cloud.CloudUtils;
3+
import com.uid2.shared.cloud.CloudStorageException;
44
import com.uid2.shared.util.URLConnectionHttpClient;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
57

8+
import java.io.InputStream;
9+
import java.net.MalformedURLException;
610
import java.net.Proxy;
7-
import java.net.http.HttpClient;
11+
import java.net.URL;
812

913
public class UidOptOutClient extends UidCoreClient {
10-
public static UidOptOutClient createNoAttest(String userToken, boolean enforceHttps, AttestationTokenRetriever attestationTokenRetriever) {
11-
return new UidOptOutClient(userToken, CloudUtils.defaultProxy, attestationTokenRetriever, null);
12-
}
14+
private static final Logger LOGGER = LoggerFactory.getLogger(UidOptOutClient.class);
15+
private AttestationResponseHandler attestationResponseHandler;
1316

1417
public UidOptOutClient(String userToken,
15-
Proxy proxy,
16-
AttestationTokenRetriever attestationTokenRetriever) {
17-
super(userToken, proxy, attestationTokenRetriever, null);
18+
Proxy proxy,
19+
AttestationResponseHandler attestationResponseHandler) {
20+
super(userToken, proxy, attestationResponseHandler, null);
21+
this.attestationResponseHandler = attestationResponseHandler;
1822
}
23+
1924
public UidOptOutClient(String userToken,
2025
Proxy proxy,
21-
AttestationTokenRetriever attestationTokenRetriever,
26+
AttestationResponseHandler attestationResponseHandler,
2227
URLConnectionHttpClient httpClient) {
23-
super(userToken, proxy, attestationTokenRetriever, httpClient);
28+
super(userToken, proxy, attestationResponseHandler, httpClient);
29+
this.attestationResponseHandler = attestationResponseHandler;
2430
}
2531

2632
@Override
2733
protected String getJWT() {
2834
return this.getAttestationTokenRetriever().getOptOutJWT();
2935
}
36+
37+
@Override
38+
public InputStream download(String path) throws CloudStorageException {
39+
if (path == null) {
40+
path = "";
41+
}
42+
43+
if (this.attestationResponseHandler.getOptOutUrl() != null) {
44+
try {
45+
URL baseUrl = new URL(this.attestationResponseHandler.getOptOutUrl());
46+
URL fullUrl = new URL(baseUrl, path);
47+
return super.download(fullUrl.toExternalForm());
48+
} catch (MalformedURLException e) {
49+
LOGGER.error("Unable to parse OptOut URL", e);
50+
} catch (Exception e) {
51+
// Specifically not logging the exception as it might contain sensitive URLs
52+
LOGGER.error("Unexpected error in UidOptOutClient download");
53+
}
54+
} else {
55+
LOGGER.warn("UidOptOutClient attempting to download but OptOutUrl not available");
56+
}
57+
58+
return InputStream.nullInputStream();
59+
}
3060
}

src/main/java/com/uid2/shared/optout/OptOutCloudSync.java

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.slf4j.Logger;
1212
import org.slf4j.LoggerFactory;
1313

14-
import java.io.File;
1514
import java.net.MalformedURLException;
1615
import java.net.URL;
1716
import java.nio.file.Path;

0 commit comments

Comments
 (0)