Skip to content

Commit eb620fe

Browse files
authored
Merge pull request #82 from scong-ttd/uid2-647-optionally-generate-token
New token-generate parameter to respect earlier optout records
2 parents 12a062f + 38ba322 commit eb620fe

File tree

12 files changed

+176
-31
lines changed

12 files changed

+176
-31
lines changed

src/main/java/com/uid2/operator/model/IdentityRequest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@
33
public final class IdentityRequest {
44
public final PublisherIdentity publisherIdentity;
55
public final UserIdentity userIdentity;
6+
public final TokenGeneratePolicy tokenGeneratePolicy;
67

7-
public IdentityRequest(PublisherIdentity publisherIdentity, UserIdentity userIdentity) {
8+
public IdentityRequest(
9+
PublisherIdentity publisherIdentity,
10+
UserIdentity userIdentity,
11+
TokenGeneratePolicy tokenGeneratePolicy)
12+
{
813
this.publisherIdentity = publisherIdentity;
914
this.userIdentity = userIdentity;
15+
this.tokenGeneratePolicy = tokenGeneratePolicy;
16+
}
17+
18+
public boolean shouldCheckOptOut() {
19+
return tokenGeneratePolicy.equals(TokenGeneratePolicy.RespectOptOut);
1020
}
1121
}

src/main/java/com/uid2/operator/model/IdentityTokens.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ public Instant getRefreshExpires() {
4444
public Instant getRefreshFrom() {
4545
return refreshFrom;
4646
}
47+
48+
public boolean isEmptyToken() {
49+
return advertisingToken == null || advertisingToken.isEmpty();
50+
}
4751
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.uid2.operator.model;
2+
3+
public enum TokenGeneratePolicy {
4+
JustGenerate(0),
5+
RespectOptOut(1);
6+
7+
public final int policy;
8+
9+
TokenGeneratePolicy(int policy) { this.policy = policy; }
10+
11+
public static TokenGeneratePolicy fromValue(int value) {
12+
switch (value) {
13+
case 0: return JustGenerate;
14+
case 1: return RespectOptOut;
15+
default: throw new IllegalArgumentException();
16+
}
17+
}
18+
19+
public static TokenGeneratePolicy defaultPolicy() {
20+
return JustGenerate;
21+
}
22+
}

src/main/java/com/uid2/operator/service/EncryptionKeyUtil.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public class EncryptionKeyUtil {
99
public static EncryptionKey getActiveSiteKey(IKeyStore.IKeyStoreSnapshot keyStoreSnapshot, int siteId, int fallbackSiteId, Instant now) {
1010
EncryptionKey key = keyStoreSnapshot.getActiveSiteKey(siteId, now);
1111
if (key == null) key = keyStoreSnapshot.getActiveSiteKey(fallbackSiteId, now);
12+
if (key == null) {
13+
throw new RuntimeException(String.format("cannot get active site key with ID %d or %d", siteId, fallbackSiteId));
14+
}
1215
return key;
1316
}
1417
}

src/main/java/com/uid2/operator/service/ResponseUtil.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ public static void SuccessV2(RoutingContext rc, Object body) {
4949
rc.data().put("response", json);
5050
}
5151

52+
public static void OptOutV2(RoutingContext rc, Object body) {
53+
final JsonObject json = new JsonObject(new HashMap<String, Object>() {
54+
{
55+
put("status", UIDOperatorVerticle.ResponseStatus.OptOut);
56+
put("body", body);
57+
}
58+
});
59+
rc.data().put("response", json);
60+
}
61+
5262
public static void ClientError(RoutingContext rc, String message) {
5363
Error(UIDOperatorVerticle.ResponseStatus.ClientError, 400, rc, message);
5464
}

src/main/java/com/uid2/operator/service/UIDOperatorService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ public IdentityTokens generateIdentity(IdentityRequest request) {
8080
final UserIdentity firstLevelHashIdentity = new UserIdentity(
8181
request.userIdentity.identityScope, request.userIdentity.identityType, firstLevelHash, request.userIdentity.privacyBits,
8282
request.userIdentity.establishedAt, request.userIdentity.refreshedAt);
83-
return generateIdentity(request.publisherIdentity, firstLevelHashIdentity);
83+
84+
if (request.shouldCheckOptOut() && hasGlobalOptOut(firstLevelHashIdentity)) {
85+
return IdentityTokens.LogoutToken;
86+
} else {
87+
return generateIdentity(request.publisherIdentity, firstLevelHashIdentity);
88+
}
8489
}
8590

8691
@Override
@@ -225,4 +230,8 @@ private UserToken createUserToken(PublisherIdentity publisherIdentity, UserIdent
225230
userIdentity);
226231
}
227232

233+
private boolean hasGlobalOptOut(UserIdentity userIdentity) {
234+
return this.optOutStore.getLatestEntry(userIdentity) != null;
235+
}
236+
228237
}

src/main/java/com/uid2/operator/service/V2RequestUtil.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static V2Request parseRequest(String bodyString, ClientKey ck) {
9999
String bodyStr = new String(decryptedBody, 16, decryptedBody.length - 16, StandardCharsets.UTF_8);
100100
payload = new JsonObject(bodyStr);
101101
} catch (Exception ex) {
102-
LOGGER.error(ex);
102+
LOGGER.error("Invalid payload in body: Data is not valid json string.", ex);
103103
return new V2Request("Invalid payload in body: Data is not valid json string.");
104104
}
105105
}
@@ -132,7 +132,7 @@ public static V2Request parseRefreshRequest(String bodyString, IKeyStore keyStor
132132
try {
133133
decrypted = AesGcm.decrypt(bytes, 5, key);
134134
} catch (Exception ex) {
135-
LOGGER.error(ex);
135+
LOGGER.error("Invalid data: Check encryption method and encryption key", ex);
136136
return new V2Request("Invalid data: Check encryption method and encryption key");
137137
}
138138

@@ -143,7 +143,7 @@ public static V2Request parseRefreshRequest(String bodyString, IKeyStore keyStor
143143

144144
return new V2Request(null, refreshToken, responseKey);
145145
} catch (Exception ex) {
146-
LOGGER.error(ex);
146+
LOGGER.error("Invalid format: Payload is not valid json or missing required data", ex);
147147
return new V2Request("Invalid format: Payload is not valid json or missing required data");
148148
}
149149
}

src/main/java/com/uid2/operator/store/IOptOutStore.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
public interface IOptOutStore {
1010

11+
/**
12+
* Get latest Opt-out record with respect to the UID (hashed identity)
13+
* @param firstLevelHashIdentity UID
14+
* @return The timestamp of latest opt-out record. <b>NULL</b> if no record.
15+
*/
1116
Instant getLatestEntry(UserIdentity firstLevelHashIdentity);
1217

1318
void addEntry(UserIdentity firstLevelHashIdentity, byte[] advertisingId, Handler<AsyncResult<Instant>> handler);

src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public void handleKeysRequestV1(RoutingContext rc) {
233233
try {
234234
handleKeysRequestCommon(rc, keys -> ResponseUtil.Success(rc, keys));
235235
} catch (Exception e) {
236-
LOGGER.error(e);
236+
LOGGER.error("Unknown error while handling keys request v1", e);
237237
rc.fail(500);
238238
}
239239
}
@@ -242,7 +242,7 @@ public void handleKeysRequestV2(RoutingContext rc) {
242242
try {
243243
handleKeysRequestCommon(rc, keys -> ResponseUtil.SuccessV2(rc, keys));
244244
} catch (Exception e) {
245-
LOGGER.error(e);
245+
LOGGER.error("Unknown error while handling keys request v2", e);
246246
rc.fail(500);
247247
}
248248
}
@@ -251,7 +251,7 @@ public void handleKeysRequest(RoutingContext rc) {
251251
try {
252252
handleKeysRequestCommon(rc, keys -> sendJsonResponse(rc, keys));
253253
} catch (Exception e) {
254-
LOGGER.error(e);
254+
LOGGER.error("Unknown error while handling keys request", e);
255255
rc.fail(500);
256256
}
257257
}
@@ -309,7 +309,7 @@ private void handleTokenRefreshV1(RoutingContext rc) {
309309
ResponseUtil.Success(rc, toJsonV1(r.getTokens()));
310310
}
311311
} catch (Exception e) {
312-
LOGGER.error(e);
312+
LOGGER.error("unknown error while refreshing token", e);
313313
ResponseUtil.Error(ResponseStatus.UnknownError, 500, rc, "Service Error");
314314
}
315315
}
@@ -336,7 +336,7 @@ private void handleTokenRefreshV2(RoutingContext rc) {
336336
ResponseUtil.SuccessV2(rc, toJsonV1(r.getTokens()));
337337
}
338338
} catch (Exception e) {
339-
LOGGER.error(e);
339+
LOGGER.error("Unknown error while refreshing token v2", e);
340340
ResponseUtil.Error(ResponseStatus.UnknownError, 500, rc, "Service Error");
341341
}
342342
}
@@ -363,7 +363,7 @@ private void handleTokenValidateV1(RoutingContext rc) {
363363
ResponseUtil.Success(rc, Boolean.FALSE);
364364
}
365365
} catch (Exception e) {
366-
LOGGER.error(e);
366+
LOGGER.error("Unknown error while validating token", e);
367367
rc.fail(500);
368368
}
369369
}
@@ -394,7 +394,7 @@ private void handleTokenValidateV2(RoutingContext rc) {
394394
ResponseUtil.SuccessV2(rc, Boolean.FALSE);
395395
}
396396
} catch (Exception e) {
397-
LOGGER.error(e);
397+
LOGGER.error("Unknown error while validating token v2", e);
398398
rc.fail(500);
399399
}
400400
}
@@ -409,14 +409,15 @@ private void handleTokenGenerateV1(RoutingContext rc) {
409409
final IdentityTokens t = this.idService.generateIdentity(
410410
new IdentityRequest(
411411
new PublisherIdentity(clientKey.getSiteId(), 0, 0),
412-
input.toUserIdentity(this.identityScope, 1, Instant.now())));
412+
input.toUserIdentity(this.identityScope, 1, Instant.now()),
413+
TokenGeneratePolicy.defaultPolicy()));
413414

414415
//Integer.parseInt(rc.queryParam("privacy_bits").get(0))));
415416

416417
ResponseUtil.Success(rc, toJsonV1(t));
417418
}
418419
} catch (Exception e) {
419-
LOGGER.error(e);
420+
LOGGER.error("Unknown error while generating token v1", e);
420421
rc.fail(500);
421422
}
422423
}
@@ -452,11 +453,20 @@ private void handleTokenGenerateV2(RoutingContext rc) {
452453
final IdentityTokens t = this.idService.generateIdentity(
453454
new IdentityRequest(
454455
new PublisherIdentity(clientKey.getSiteId(), 0, 0),
455-
input.toUserIdentity(this.identityScope, 1, Instant.now())));
456-
ResponseUtil.SuccessV2(rc, toJsonV1(t));
456+
input.toUserIdentity(this.identityScope, 1, Instant.now()),
457+
readTokenGeneratePolicy(req)));
458+
459+
if (t.isEmptyToken()) {
460+
ResponseUtil.OptOutV2(rc, toJsonV1(t));
461+
} else {
462+
ResponseUtil.SuccessV2(rc, toJsonV1(t));
463+
}
457464
}
465+
} catch (IllegalArgumentException iae) {
466+
LOGGER.warn("request body contains invalid argument(s)", iae);
467+
ResponseUtil.ClientError(rc, "request body contains invalid argument(s)");
458468
} catch (Exception e) {
459-
LOGGER.error(e);
469+
LOGGER.error("Unknown error while generating token v2", e);
460470
rc.fail(500);
461471
}
462472
}
@@ -473,14 +483,15 @@ private void handleTokenGenerate(RoutingContext rc) {
473483
final IdentityTokens t = this.idService.generateIdentity(
474484
new IdentityRequest(
475485
new PublisherIdentity(clientKey.getSiteId(), 0, 0),
476-
input.toUserIdentity(this.identityScope, 1, Instant.now())));
486+
input.toUserIdentity(this.identityScope, 1, Instant.now()),
487+
TokenGeneratePolicy.defaultPolicy()));
477488

478489
//Integer.parseInt(rc.queryParam("privacy_bits").get(0))));
479490

480491
sendJsonResponse(rc, toJson(t));
481492

482493
} catch (Exception e) {
483-
LOGGER.error(e);
494+
LOGGER.error("Unknown error while generating token", e);
484495
rc.fail(500);
485496
}
486497
}
@@ -496,7 +507,7 @@ private void handleTokenRefresh(RoutingContext rc) {
496507
final RefreshResponse r = this.refreshIdentity(rc, tokenList.get(0));
497508
sendJsonResponse(rc, toJson(r.getTokens()));
498509
} catch (Exception e) {
499-
LOGGER.error(e);
510+
LOGGER.error("Unknown error while refreshing token", e);
500511
rc.fail(500);
501512
}
502513
}
@@ -519,7 +530,7 @@ private void handleValidate(RoutingContext rc) {
519530
rc.response().end("not allowed");
520531
}
521532
} catch (Exception e) {
522-
LOGGER.error(e);
533+
LOGGER.error("Unknown error while validating token", e);
523534
rc.fail(500);
524535
}
525536
}
@@ -577,7 +588,7 @@ private void handleOptOutGet(RoutingContext rc) {
577588
.write(String.valueOf(timestamp))
578589
.end();
579590
} catch (Exception ex) {
580-
LOGGER.error(ex);
591+
LOGGER.error("Unexpected error while handling optout get", ex);
581592
rc.fail(500);
582593
}
583594
} else {
@@ -659,7 +670,7 @@ private void handleIdentityMapV1(RoutingContext rc) {
659670
jsonObject.put("bucket_id", mappedIdentity.bucketId);
660671
ResponseUtil.Success(rc, jsonObject);
661672
} catch (Exception e) {
662-
LOGGER.error(e);
673+
LOGGER.error("Unknown error while mapping identity v1", e);
663674
ResponseUtil.Error(ResponseStatus.UnknownError, 500, rc, "Unknown State");
664675
}
665676
}
@@ -677,7 +688,7 @@ private void handleIdentityMap(RoutingContext rc) {
677688
}
678689
}
679690
catch (Exception ex) {
680-
LOGGER.error(ex);
691+
LOGGER.error("Unexpected error while mapping identity", ex);
681692
rc.fail(500);
682693
}
683694
}
@@ -887,7 +898,7 @@ private void handleIdentityMapBatchV1(RoutingContext rc) {
887898
resp.put("mapped", mapped);
888899
ResponseUtil.Success(rc, resp);
889900
} catch (Exception e) {
890-
LOGGER.error(e);
901+
LOGGER.error("Unknown error while mapping batched identity", e);
891902
ResponseUtil.Error(ResponseStatus.UnknownError, 500, rc, "Unknown State");
892903
}
893904
}
@@ -924,7 +935,7 @@ private void handleIdentityMapV2(RoutingContext rc) {
924935
resp.put("mapped", mapped);
925936
ResponseUtil.SuccessV2(rc, resp);
926937
} catch (Exception e) {
927-
LOGGER.error(e);
938+
LOGGER.error("Unknown error while mapping identity v2", e);
928939
ResponseUtil.Error(ResponseStatus.UnknownError, 500, rc, "Unknown State");
929940
}
930941
}
@@ -1011,7 +1022,7 @@ private void handleIdentityMapBatch(RoutingContext rc) {
10111022
resp.put("mapped", mapped);
10121023
sendJsonResponse(rc, resp);
10131024
} catch (Exception e) {
1014-
LOGGER.error(e);
1025+
LOGGER.error("Unknown error while mapping batched identity", e);
10151026
rc.fail(500);
10161027
}
10171028
}
@@ -1148,6 +1159,13 @@ private UserConsentStatus validateUserConsent(JsonObject req) {
11481159
return UserConsentStatus.SUFFICIENT;
11491160
}
11501161

1162+
private static final String TOKEN_GENERATE_POLICY_PARAM = "policy";
1163+
private TokenGeneratePolicy readTokenGeneratePolicy(JsonObject req) {
1164+
return req.containsKey(TOKEN_GENERATE_POLICY_PARAM) ?
1165+
TokenGeneratePolicy.fromValue(req.getInteger(TOKEN_GENERATE_POLICY_PARAM)) :
1166+
TokenGeneratePolicy.defaultPolicy();
1167+
}
1168+
11511169
private TransparentConsentParseResult getUserConsentV2(JsonObject req) {
11521170
final String rawTcString = req.getString("tcf_consent_string");
11531171
if (rawTcString == null || rawTcString.isEmpty()) {

src/main/java/com/uid2/operator/vertx/V2PayloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void handleTokenGenerate(RoutingContext rc, Handler<RoutingContext> apiHa
9494
JsonObject respJson = (JsonObject) rc.data().get("response");
9595

9696
// DevNote: 200 does not guarantee a token.
97-
if (respJson.containsKey("body")) {
97+
if (respJson.getString("status").equals(UIDOperatorVerticle.ResponseStatus.Success) && respJson.containsKey("body")) {
9898
V2RequestUtil.handleRefreshTokenInResponseBody(respJson.getJsonObject("body"), keyStore, this.identityScope);
9999
}
100100

0 commit comments

Comments
 (0)