diff --git a/src/main/java/com/uid2/operator/model/AdvertisingToken.java b/src/main/java/com/uid2/operator/model/AdvertisingToken.java deleted file mode 100644 index e1fcc4725..000000000 --- a/src/main/java/com/uid2/operator/model/AdvertisingToken.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.uid2.operator.model; - -import java.time.Instant; -import com.uid2.shared.model.TokenVersion; - -public class AdvertisingToken extends VersionedToken { - public final OperatorIdentity operatorIdentity; - public final PublisherIdentity publisherIdentity; - public final UserIdentity userIdentity; - - public AdvertisingToken(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, - PublisherIdentity publisherIdentity, UserIdentity userIdentity) { - super(version, createdAt, expiresAt); - this.operatorIdentity = operatorIdentity; - this.publisherIdentity = publisherIdentity; - this.userIdentity = userIdentity; - } -} - diff --git a/src/main/java/com/uid2/operator/model/AdvertisingTokenRequest.java b/src/main/java/com/uid2/operator/model/AdvertisingTokenRequest.java new file mode 100644 index 000000000..d63fa66a8 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/AdvertisingTokenRequest.java @@ -0,0 +1,28 @@ +package com.uid2.operator.model; + +import java.time.Instant; + +import com.uid2.operator.model.identities.RawUid; +import com.uid2.operator.util.PrivacyBits; +import com.uid2.shared.model.TokenVersion; + +// class containing enough information to create a new uid token (aka advertising token) +public class AdvertisingTokenRequest extends VersionedTokenRequest { + public final OperatorIdentity operatorIdentity; + public final SourcePublisher sourcePublisher; + public final RawUid rawUid; + public final PrivacyBits privacyBits; + public final Instant establishedAt; + + public AdvertisingTokenRequest(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, + SourcePublisher sourcePublisher, RawUid rawUid, PrivacyBits privacyBits, + Instant establishedAt) { + super(version, createdAt, expiresAt); + this.operatorIdentity = operatorIdentity; + this.sourcePublisher = sourcePublisher; + this.rawUid = rawUid; + this.privacyBits = privacyBits; + this.establishedAt = establishedAt; + } +} + diff --git a/src/main/java/com/uid2/operator/model/MapRequest.java b/src/main/java/com/uid2/operator/model/IdentityMapRequestItem.java similarity index 60% rename from src/main/java/com/uid2/operator/model/MapRequest.java rename to src/main/java/com/uid2/operator/model/IdentityMapRequestItem.java index d39e87238..079af8e76 100644 --- a/src/main/java/com/uid2/operator/model/MapRequest.java +++ b/src/main/java/com/uid2/operator/model/IdentityMapRequestItem.java @@ -1,18 +1,19 @@ package com.uid2.operator.model; +import com.uid2.operator.model.identities.HashedDii; + import java.time.Instant; -public final class MapRequest { - public final UserIdentity userIdentity; +public final class IdentityMapRequestItem { + public final HashedDii hashedDii; public final OptoutCheckPolicy optoutCheckPolicy; public final Instant asOf; - public MapRequest( - UserIdentity userIdentity, + public IdentityMapRequestItem( + HashedDii hashedDii, OptoutCheckPolicy optoutCheckPolicy, - Instant asOf) - { - this.userIdentity = userIdentity; + Instant asOf) { + this.hashedDii = hashedDii; this.optoutCheckPolicy = optoutCheckPolicy; this.asOf = asOf; } diff --git a/src/main/java/com/uid2/operator/model/IdentityMapResponseItem.java b/src/main/java/com/uid2/operator/model/IdentityMapResponseItem.java new file mode 100644 index 000000000..909596a2f --- /dev/null +++ b/src/main/java/com/uid2/operator/model/IdentityMapResponseItem.java @@ -0,0 +1,19 @@ +package com.uid2.operator.model; + +// Contains the computed raw UID and its bucket ID from identity/map request +public class IdentityMapResponseItem { + public static final IdentityMapResponseItem OptoutIdentity = new IdentityMapResponseItem(new byte[33], ""); + // The raw UID is also known as Advertising Id (historically) + public final byte[] rawUid; + public final String bucketId; + + public IdentityMapResponseItem(byte[] rawUid, String bucketId) { + this.rawUid = rawUid; + this.bucketId = bucketId; + } + + // historically Optout is known as Logout + public boolean isOptedOut() { + return this.equals(OptoutIdentity) || this.bucketId == null || this.bucketId.isEmpty(); + } +} diff --git a/src/main/java/com/uid2/operator/model/IdentityRequest.java b/src/main/java/com/uid2/operator/model/IdentityRequest.java deleted file mode 100644 index 74d8917dd..000000000 --- a/src/main/java/com/uid2/operator/model/IdentityRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.uid2.operator.model; - -public final class IdentityRequest { - public final PublisherIdentity publisherIdentity; - public final UserIdentity userIdentity; - public final OptoutCheckPolicy optoutCheckPolicy; - - public IdentityRequest( - PublisherIdentity publisherIdentity, - UserIdentity userIdentity, - OptoutCheckPolicy tokenGeneratePolicy) - { - this.publisherIdentity = publisherIdentity; - this.userIdentity = userIdentity; - this.optoutCheckPolicy = tokenGeneratePolicy; - } - - public boolean shouldCheckOptOut() { - return optoutCheckPolicy.equals(OptoutCheckPolicy.RespectOptOut); - } -} diff --git a/src/main/java/com/uid2/operator/model/IdentityTokens.java b/src/main/java/com/uid2/operator/model/IdentityTokens.java deleted file mode 100644 index 76dd2c4d7..000000000 --- a/src/main/java/com/uid2/operator/model/IdentityTokens.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.uid2.operator.model; - -import com.uid2.shared.model.TokenVersion; - -import java.time.Instant; - -public class IdentityTokens { - public static IdentityTokens LogoutToken = new IdentityTokens("", null, "", Instant.EPOCH, Instant.EPOCH, Instant.EPOCH); - private final String advertisingToken; - private final TokenVersion advertisingTokenVersion; - private final String refreshToken; - private final Instant identityExpires; - private final Instant refreshExpires; - private final Instant refreshFrom; - - public IdentityTokens(String advertisingToken, TokenVersion advertisingTokenVersion, String refreshToken, - Instant identityExpires, Instant refreshExpires, Instant refreshFrom) { - this.advertisingToken = advertisingToken; - this.advertisingTokenVersion = advertisingTokenVersion; - this.refreshToken = refreshToken; - this.identityExpires = identityExpires; - this.refreshExpires = refreshExpires; - this.refreshFrom = refreshFrom; - } - - public String getAdvertisingToken() { - return advertisingToken; - } - - public TokenVersion getAdvertisingTokenVersion() { - return advertisingTokenVersion; - } - - public String getRefreshToken() { - return refreshToken; - } - - public Instant getIdentityExpires() { - return identityExpires; - } - - public Instant getRefreshExpires() { - return refreshExpires; - } - - public Instant getRefreshFrom() { - return refreshFrom; - } - - public boolean isEmptyToken() { - return advertisingToken == null || advertisingToken.isEmpty(); - } -} diff --git a/src/main/java/com/uid2/operator/model/MappedIdentity.java b/src/main/java/com/uid2/operator/model/MappedIdentity.java deleted file mode 100644 index dfa2899f4..000000000 --- a/src/main/java/com/uid2/operator/model/MappedIdentity.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.uid2.operator.model; - -public class MappedIdentity { - public static MappedIdentity LogoutIdentity = new MappedIdentity(new byte[33], ""); - public final byte[] advertisingId; - public final String bucketId; - - public MappedIdentity(byte[] advertisingId, String bucketId) { - this.advertisingId = advertisingId; - this.bucketId = bucketId; - } - - public boolean isOptedOut() { - return this.equals(LogoutIdentity) || this.bucketId == null || this.bucketId.isEmpty(); - } -} diff --git a/src/main/java/com/uid2/operator/model/PublisherIdentity.java b/src/main/java/com/uid2/operator/model/PublisherIdentity.java deleted file mode 100644 index d7a0fae57..000000000 --- a/src/main/java/com/uid2/operator/model/PublisherIdentity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.uid2.operator.model; - -public class PublisherIdentity { - public final int siteId; - public final int clientKeyId; - public final long publisherId; - - public PublisherIdentity(int siteId, int clientKeyId, long publisherId) { - this.siteId = siteId; - this.clientKeyId = clientKeyId; - this.publisherId = publisherId; - } -} diff --git a/src/main/java/com/uid2/operator/model/RefreshResponse.java b/src/main/java/com/uid2/operator/model/RefreshResponse.java deleted file mode 100644 index fbe41f96b..000000000 --- a/src/main/java/com/uid2/operator/model/RefreshResponse.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.uid2.operator.model; - -import java.time.Duration; - -public class RefreshResponse { - - public static RefreshResponse Invalid = new RefreshResponse(Status.Invalid, IdentityTokens.LogoutToken); - public static RefreshResponse Optout = new RefreshResponse(Status.Optout, IdentityTokens.LogoutToken); - public static RefreshResponse Expired = new RefreshResponse(Status.Expired, IdentityTokens.LogoutToken); - public static RefreshResponse Deprecated = new RefreshResponse(Status.Deprecated, IdentityTokens.LogoutToken); - public static RefreshResponse NoActiveKey = new RefreshResponse(Status.NoActiveKey, IdentityTokens.LogoutToken); - private final Status status; - private final IdentityTokens tokens; - private final Duration durationSinceLastRefresh; - private final boolean isCstg; - - private RefreshResponse(Status status, IdentityTokens tokens, Duration durationSinceLastRefresh, boolean isCstg) { - this.status = status; - this.tokens = tokens; - this.durationSinceLastRefresh = durationSinceLastRefresh; - this.isCstg = isCstg; - } - - private RefreshResponse(Status status, IdentityTokens tokens) { - this(status, tokens, null, false); - } - - public static RefreshResponse createRefreshedResponse(IdentityTokens tokens, Duration durationSinceLastRefresh, boolean isCstg) { - return new RefreshResponse(Status.Refreshed, tokens, durationSinceLastRefresh, isCstg); - } - - public Status getStatus() { - return status; - } - - public IdentityTokens getTokens() { - return tokens; - } - - public Duration getDurationSinceLastRefresh() { - return durationSinceLastRefresh; - } - - public boolean isCstg() { return isCstg;} - - public boolean isRefreshed() { - return Status.Refreshed.equals(this.status); - } - - public boolean isOptOut() { - return Status.Optout.equals(this.status); - } - - public boolean isInvalidToken() { - return Status.Invalid.equals(this.status); - } - - public boolean isDeprecated() { - return Status.Deprecated.equals(this.status); - } - - public boolean isExpired() { - return Status.Expired.equals(this.status); - } - - public boolean noActiveKey() { - return Status.NoActiveKey.equals(this.status); - } - - public enum Status { - Refreshed, - Invalid, - Optout, - Expired, - Deprecated, - NoActiveKey - } - -} diff --git a/src/main/java/com/uid2/operator/model/RefreshToken.java b/src/main/java/com/uid2/operator/model/RefreshToken.java deleted file mode 100644 index 8c70b3bf9..000000000 --- a/src/main/java/com/uid2/operator/model/RefreshToken.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.uid2.operator.model; - -import java.time.Instant; -import com.uid2.shared.model.TokenVersion; - -public class RefreshToken extends VersionedToken { - public final OperatorIdentity operatorIdentity; - public final PublisherIdentity publisherIdentity; - public final UserIdentity userIdentity; - - public RefreshToken(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, - PublisherIdentity publisherIdentity, UserIdentity userIdentity) { - super(version, createdAt, expiresAt); - this.operatorIdentity = operatorIdentity; - this.publisherIdentity = publisherIdentity; - this.userIdentity = userIdentity; - } -} diff --git a/src/main/java/com/uid2/operator/model/SourcePublisher.java b/src/main/java/com/uid2/operator/model/SourcePublisher.java new file mode 100644 index 000000000..bd19740a1 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/SourcePublisher.java @@ -0,0 +1,24 @@ +package com.uid2.operator.model; + +// The original publisher that requests to generate a UID token +public class SourcePublisher { + public final int siteId; + + // these 2 values are added into adverting/UID token and refresh token payload but + // are not really used for any real purposes currently so sometimes are set to 0 + // see the constructor below + public final int clientKeyId; + public final long publisherId; + + public SourcePublisher(int siteId, int clientKeyId, long publisherId) { + this.siteId = siteId; + this.clientKeyId = clientKeyId; + this.publisherId = publisherId; + } + + public SourcePublisher(int siteId) { + this.siteId = siteId; + this.clientKeyId = 0; + this.publisherId = 0; + } +} diff --git a/src/main/java/com/uid2/operator/model/TokenGenerateRequest.java b/src/main/java/com/uid2/operator/model/TokenGenerateRequest.java new file mode 100644 index 000000000..39f3b56fc --- /dev/null +++ b/src/main/java/com/uid2/operator/model/TokenGenerateRequest.java @@ -0,0 +1,40 @@ +package com.uid2.operator.model; + +import com.uid2.operator.model.identities.HashedDii; +import com.uid2.operator.util.PrivacyBits; + +import java.time.Instant; + +public final class TokenGenerateRequest { + public final SourcePublisher sourcePublisher; + public final HashedDii hashedDii; + public final OptoutCheckPolicy optoutCheckPolicy; + + public final PrivacyBits privacyBits; + public final Instant establishedAt; + + public TokenGenerateRequest( + SourcePublisher sourcePublisher, + HashedDii hashedDii, + OptoutCheckPolicy tokenGeneratePolicy, + PrivacyBits privacyBits, + Instant establishedAt) { + this.sourcePublisher = sourcePublisher; + this.hashedDii = hashedDii; + this.optoutCheckPolicy = tokenGeneratePolicy; + this.privacyBits = privacyBits; + this.establishedAt = establishedAt; + } + + public TokenGenerateRequest( + SourcePublisher sourcePublisher, + HashedDii hashedDii, + OptoutCheckPolicy tokenGeneratePolicy) { + this(sourcePublisher, hashedDii, tokenGeneratePolicy, PrivacyBits.DEFAULT, Instant.now()); + + } + + public boolean shouldCheckOptOut() { + return optoutCheckPolicy.equals(OptoutCheckPolicy.RespectOptOut); + } +} diff --git a/src/main/java/com/uid2/operator/model/TokenGenerateResponse.java b/src/main/java/com/uid2/operator/model/TokenGenerateResponse.java new file mode 100644 index 000000000..8c7d273f9 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/TokenGenerateResponse.java @@ -0,0 +1,81 @@ +package com.uid2.operator.model; + +import com.uid2.shared.model.TokenVersion; +import io.vertx.core.json.JsonObject; + +import java.time.Instant; + +// this defines all the fields for the response of the /token/generate and /client/generate endpoints before they are +// jsonified +// todo: can be converted to record later +public class TokenGenerateResponse { + public static final TokenGenerateResponse OptOutResponse = new TokenGenerateResponse("", null, "", Instant.EPOCH, Instant.EPOCH, Instant.EPOCH); + + //aka UID token + private final String advertisingToken; + private final TokenVersion advertisingTokenVersion; + private final String refreshToken; + // when the advertising token/uid token expires + private final Instant identityExpires; + private final Instant refreshExpires; + private final Instant refreshFrom; + + public TokenGenerateResponse(String advertisingToken, TokenVersion advertisingTokenVersion, String refreshToken, + Instant identityExpires, Instant refreshExpires, Instant refreshFrom) { + this.advertisingToken = advertisingToken; + this.advertisingTokenVersion = advertisingTokenVersion; + this.refreshToken = refreshToken; + this.identityExpires = identityExpires; + this.refreshExpires = refreshExpires; + this.refreshFrom = refreshFrom; + } + + public String getAdvertisingToken() { + return advertisingToken; + } + + public TokenVersion getAdvertisingTokenVersion() { + return advertisingTokenVersion; + } + + public String getRefreshToken() { + return refreshToken; + } + + public Instant getIdentityExpires() { + return identityExpires; + } + + public Instant getRefreshExpires() { + return refreshExpires; + } + + public Instant getRefreshFrom() { + return refreshFrom; + } + + public boolean isOptedOut() { + return advertisingToken == null || advertisingToken.isEmpty(); + } + + // for v1/v2 token/generate and token/refresh and client/generate (CSTG) endpoints + public JsonObject toJsonV1() { + final JsonObject json = new JsonObject(); + json.put("advertising_token", getAdvertisingToken()); + json.put("refresh_token", getRefreshToken()); + json.put("identity_expires", getIdentityExpires().toEpochMilli()); + json.put("refresh_expires", getRefreshExpires().toEpochMilli()); + json.put("refresh_from", getRefreshFrom().toEpochMilli()); + return json; + } + + // for the original/legacy token/generate and token/refresh endpoint + public JsonObject toJsonV0() { + final JsonObject json = new JsonObject(); + json.put("advertisement_token", getAdvertisingToken()); + json.put("advertising_token", getAdvertisingToken()); + json.put("refresh_token", getRefreshToken()); + + return json; + } +} diff --git a/src/main/java/com/uid2/operator/model/TokenRefreshRequest.java b/src/main/java/com/uid2/operator/model/TokenRefreshRequest.java new file mode 100644 index 000000000..e2e51971a --- /dev/null +++ b/src/main/java/com/uid2/operator/model/TokenRefreshRequest.java @@ -0,0 +1,26 @@ +package com.uid2.operator.model; + +import java.time.Instant; + +import com.uid2.operator.model.identities.FirstLevelHash; +import com.uid2.operator.util.PrivacyBits; +import com.uid2.shared.model.TokenVersion; + +// class containing enough data to create a new refresh token +public class TokenRefreshRequest extends VersionedTokenRequest { + public final OperatorIdentity operatorIdentity; + public final SourcePublisher sourcePublisher; + public final FirstLevelHash firstLevelHash; + // by default, inherited from the previous refresh token's privacy bits + public final PrivacyBits privacyBits; + + + public TokenRefreshRequest(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, + SourcePublisher sourcePublisher, FirstLevelHash firstLevelHash, PrivacyBits privacyBits) { + super(version, createdAt, expiresAt); + this.operatorIdentity = operatorIdentity; + this.sourcePublisher = sourcePublisher; + this.firstLevelHash = firstLevelHash; + this.privacyBits = privacyBits; + } +} diff --git a/src/main/java/com/uid2/operator/model/TokenRefreshResponse.java b/src/main/java/com/uid2/operator/model/TokenRefreshResponse.java new file mode 100644 index 000000000..40e5d73c9 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/TokenRefreshResponse.java @@ -0,0 +1,80 @@ +package com.uid2.operator.model; + +import java.time.Duration; + +public class TokenRefreshResponse { + + public static final TokenRefreshResponse Invalid = new TokenRefreshResponse(Status.Invalid, + TokenGenerateResponse.OptOutResponse); + public static final TokenRefreshResponse Optout = new TokenRefreshResponse(Status.Optout, TokenGenerateResponse.OptOutResponse); + public static final TokenRefreshResponse Expired = new TokenRefreshResponse(Status.Expired, TokenGenerateResponse.OptOutResponse); + public static final TokenRefreshResponse Deprecated = new TokenRefreshResponse(Status.Deprecated, TokenGenerateResponse.OptOutResponse); + public static final TokenRefreshResponse NoActiveKey = new TokenRefreshResponse(Status.NoActiveKey, TokenGenerateResponse.OptOutResponse); + private final Status status; + private final TokenGenerateResponse tokenGenerateResponse; + private final Duration durationSinceLastRefresh; + private final boolean isCstg; + + private TokenRefreshResponse(Status status, TokenGenerateResponse tokenGenerateResponse, Duration durationSinceLastRefresh, boolean isCstg) { + this.status = status; + this.tokenGenerateResponse = tokenGenerateResponse; + this.durationSinceLastRefresh = durationSinceLastRefresh; + this.isCstg = isCstg; + } + + private TokenRefreshResponse(Status status, TokenGenerateResponse tokenGenerateResponse) { + this(status, tokenGenerateResponse, null, false); + } + + public static TokenRefreshResponse createRefreshedResponse(TokenGenerateResponse tokenGenerateResponse, Duration durationSinceLastRefresh, boolean isCstg) { + return new TokenRefreshResponse(Status.Refreshed, tokenGenerateResponse, durationSinceLastRefresh, isCstg); + } + + public Status getStatus() { + return status; + } + + public TokenGenerateResponse getIdentityResponse() { + return tokenGenerateResponse; + } + + public Duration getDurationSinceLastRefresh() { + return durationSinceLastRefresh; + } + + public boolean isCstg() { return isCstg;} + + public boolean isRefreshed() { + return Status.Refreshed.equals(this.status); + } + + public boolean isOptOut() { + return Status.Optout.equals(this.status); + } + + public boolean isInvalidToken() { + return Status.Invalid.equals(this.status); + } + + public boolean isDeprecated() { + return Status.Deprecated.equals(this.status); + } + + public boolean isExpired() { + return Status.Expired.equals(this.status); + } + + public boolean noActiveKey() { + return Status.NoActiveKey.equals(this.status); + } + + public enum Status { + Refreshed, + Invalid, + Optout, + Expired, + Deprecated, + NoActiveKey + } + +} diff --git a/src/main/java/com/uid2/operator/model/UserIdentity.java b/src/main/java/com/uid2/operator/model/UserIdentity.java deleted file mode 100644 index 760d0ffb6..000000000 --- a/src/main/java/com/uid2/operator/model/UserIdentity.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.uid2.operator.model; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Objects; - -public class UserIdentity { - public final IdentityScope identityScope; - public final IdentityType identityType; - public final byte[] id; - public final int privacyBits; - public final Instant establishedAt; - public final Instant refreshedAt; - - public UserIdentity(IdentityScope identityScope, IdentityType identityType, byte[] id, int privacyBits, - Instant establishedAt, Instant refreshedAt) { - this.identityScope = identityScope; - this.identityType = identityType; - this.id = id; - this.privacyBits = privacyBits; - this.establishedAt = establishedAt; - this.refreshedAt = refreshedAt; - } - - public boolean matches(UserIdentity that) { - return this.identityScope.equals(that.identityScope) && - this.identityType.equals(that.identityType) && - Arrays.equals(this.id, that.id); - } -} diff --git a/src/main/java/com/uid2/operator/model/VersionedToken.java b/src/main/java/com/uid2/operator/model/VersionedTokenRequest.java similarity index 68% rename from src/main/java/com/uid2/operator/model/VersionedToken.java rename to src/main/java/com/uid2/operator/model/VersionedTokenRequest.java index 5be86b80e..5cc9c5335 100644 --- a/src/main/java/com/uid2/operator/model/VersionedToken.java +++ b/src/main/java/com/uid2/operator/model/VersionedTokenRequest.java @@ -1,16 +1,16 @@ package com.uid2.operator.model; import java.time.Instant; -import java.util.Objects; + import com.uid2.shared.model.TokenVersion; -public abstract class VersionedToken { +public abstract class VersionedTokenRequest { public final TokenVersion version; public final Instant createdAt; public final Instant expiresAt; - public VersionedToken(TokenVersion version, Instant createdAt, Instant expiresAt) { + public VersionedTokenRequest(TokenVersion version, Instant createdAt, Instant expiresAt) { this.version = version; this.createdAt = createdAt; this.expiresAt = expiresAt; diff --git a/src/main/java/com/uid2/operator/model/IdentityType.java b/src/main/java/com/uid2/operator/model/identities/DiiType.java similarity index 67% rename from src/main/java/com/uid2/operator/model/IdentityType.java rename to src/main/java/com/uid2/operator/model/identities/DiiType.java index b64817df5..062b55d35 100644 --- a/src/main/java/com/uid2/operator/model/IdentityType.java +++ b/src/main/java/com/uid2/operator/model/identities/DiiType.java @@ -1,15 +1,15 @@ -package com.uid2.operator.model; +package com.uid2.operator.model.identities; import com.uid2.operator.vertx.ClientInputValidationException; -public enum IdentityType { +public enum DiiType { Email(0), Phone(1); public final int value; - IdentityType(int value) { this.value = value; } + DiiType(int value) { this.value = value; } - public static IdentityType fromValue(int value) { + public static DiiType fromValue(int value) { switch (value) { case 0: return Email; case 1: return Phone; diff --git a/src/main/java/com/uid2/operator/model/identities/FirstLevelHash.java b/src/main/java/com/uid2/operator/model/identities/FirstLevelHash.java new file mode 100644 index 000000000..49b2728f4 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/identities/FirstLevelHash.java @@ -0,0 +1,19 @@ +package com.uid2.operator.model.identities; + +import java.time.Instant; +import java.util.Arrays; + +/** + * Contains a first level salted hash computed from Hashed DII (email/phone number) + * @param establishedAt for brand new token generation, it should be the time it is generated if the first level hash is from token/refresh call, it will be when the raw UID was originally created in the earliest token generation + */ +public record FirstLevelHash(IdentityScope identityScope, DiiType diiType, byte[] firstLevelHash, + Instant establishedAt) { + + // explicitly not checking establishedAt - this is only for making sure the first level hash matches a new input + public boolean matches(FirstLevelHash that) { + return this.identityScope.equals(that.identityScope) && + this.diiType.equals(that.diiType) && + Arrays.equals(this.firstLevelHash, that.firstLevelHash); + } +} diff --git a/src/main/java/com/uid2/operator/model/identities/HashedDii.java b/src/main/java/com/uid2/operator/model/identities/HashedDii.java new file mode 100644 index 000000000..64c7bbf0f --- /dev/null +++ b/src/main/java/com/uid2/operator/model/identities/HashedDii.java @@ -0,0 +1,7 @@ +package com.uid2.operator.model.identities; + +// Contains a hash Directly Identifying Information (DII) (email or phone) see https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii +// This hash can either be computed from a raw email/phone number DII input or provided by the UID Participant directly +// +public record HashedDii(IdentityScope identityScope, DiiType diiType, byte[] hashedDii) { +} diff --git a/src/main/java/com/uid2/operator/IdentityConst.java b/src/main/java/com/uid2/operator/model/identities/IdentityConst.java similarity index 65% rename from src/main/java/com/uid2/operator/IdentityConst.java rename to src/main/java/com/uid2/operator/model/identities/IdentityConst.java index ae1ad6974..63fa62f96 100644 --- a/src/main/java/com/uid2/operator/IdentityConst.java +++ b/src/main/java/com/uid2/operator/model/identities/IdentityConst.java @@ -1,20 +1,24 @@ -package com.uid2.operator; +package com.uid2.operator.model.identities; import com.uid2.operator.service.EncodingUtils; public class IdentityConst { - + // DIIs for generating optout tokens for legacy participants - to be deprecated public static final String OptOutTokenIdentityForEmail = "optout@unifiedid.com"; public static final String OptOutTokenIdentityForPhone = "+00000000001"; + + // DIIs for testing with token/validate endpoint, see https://unifiedid.com/docs/endpoints/post-token-validate public static final String ValidateIdentityForEmail = "validate@example.com"; public static final String ValidateIdentityForPhone = "+12345678901"; public static final byte[] ValidateIdentityForEmailHash = EncodingUtils.getSha256Bytes(IdentityConst.ValidateIdentityForEmail); public static final byte[] ValidateIdentityForPhoneHash = EncodingUtils.getSha256Bytes(IdentityConst.ValidateIdentityForPhone); + + // DIIs to use when you want to generate an optout response in token generation or identity map public static final String OptOutIdentityForEmail = "optout@example.com"; public static final String OptOutIdentityForPhone = "+00000000000"; + + // DIIs to use when you want to generate a UID token but when doing refresh token, you want to always get an optout response + // to test the optout handling workflow public static final String RefreshOptOutIdentityForEmail = "refresh-optout@example.com"; public static final String RefreshOptOutIdentityForPhone = "+00000000002"; - - - } diff --git a/src/main/java/com/uid2/operator/model/IdentityScope.java b/src/main/java/com/uid2/operator/model/identities/IdentityScope.java similarity index 94% rename from src/main/java/com/uid2/operator/model/IdentityScope.java rename to src/main/java/com/uid2/operator/model/identities/IdentityScope.java index 0bff1edc1..3dc19a764 100644 --- a/src/main/java/com/uid2/operator/model/IdentityScope.java +++ b/src/main/java/com/uid2/operator/model/identities/IdentityScope.java @@ -1,4 +1,4 @@ -package com.uid2.operator.model; +package com.uid2.operator.model.identities; import com.uid2.operator.vertx.ClientInputValidationException; diff --git a/src/main/java/com/uid2/operator/model/identities/RawUid.java b/src/main/java/com/uid2/operator/model/identities/RawUid.java new file mode 100644 index 000000000..4ae619d00 --- /dev/null +++ b/src/main/java/com/uid2/operator/model/identities/RawUid.java @@ -0,0 +1,13 @@ +package com.uid2.operator.model.identities; + +import java.util.Arrays; + +// A raw UID is stored inside +public record RawUid(IdentityScope identityScope, DiiType diiType, byte[] rawUid) { + + public boolean matches(RawUid that) { + return this.identityScope.equals(that.identityScope) && + this.diiType.equals(that.diiType) && + Arrays.equals(this.rawUid, that.rawUid); + } +} diff --git a/src/main/java/com/uid2/operator/monitoring/TokenResponseStatsCollector.java b/src/main/java/com/uid2/operator/monitoring/TokenResponseStatsCollector.java index a61446aa9..c5f46cc7e 100644 --- a/src/main/java/com/uid2/operator/monitoring/TokenResponseStatsCollector.java +++ b/src/main/java/com/uid2/operator/monitoring/TokenResponseStatsCollector.java @@ -1,6 +1,6 @@ package com.uid2.operator.monitoring; -import com.uid2.operator.model.RefreshResponse; +import com.uid2.operator.model.TokenRefreshResponse; import com.uid2.operator.vertx.UIDOperatorVerticle; import com.uid2.shared.model.TokenVersion; import com.uid2.shared.store.ISiteStore; @@ -69,17 +69,17 @@ private static void recordInternal(ISiteStore siteStore, Integer siteId, Endpoin builder.register(Metrics.globalRegistry).increment(); } - public static void recordRefresh(ISiteStore siteStore, Integer siteId, Endpoint endpoint, RefreshResponse refreshResponse, PlatformType platformType) { + public static void recordRefresh(ISiteStore siteStore, Integer siteId, Endpoint endpoint, TokenRefreshResponse refreshResponse, PlatformType platformType) { if (!refreshResponse.isRefreshed()) { if (refreshResponse.isOptOut() || refreshResponse.isDeprecated()) { - recordInternal(siteStore, siteId, endpoint, ResponseStatus.OptOut, refreshResponse.getTokens().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); + recordInternal(siteStore, siteId, endpoint, ResponseStatus.OptOut, refreshResponse.getIdentityResponse().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); } else if (refreshResponse.isInvalidToken()) { - recordInternal(siteStore, siteId, endpoint, ResponseStatus.InvalidToken, refreshResponse.getTokens().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); + recordInternal(siteStore, siteId, endpoint, ResponseStatus.InvalidToken, refreshResponse.getIdentityResponse().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); } else if (refreshResponse.isExpired()) { - recordInternal(siteStore, siteId, endpoint, ResponseStatus.ExpiredToken, refreshResponse.getTokens().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); + recordInternal(siteStore, siteId, endpoint, ResponseStatus.ExpiredToken, refreshResponse.getIdentityResponse().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); } } else { - recordInternal(siteStore, siteId, endpoint, ResponseStatus.Success, refreshResponse.getTokens().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); + recordInternal(siteStore, siteId, endpoint, ResponseStatus.Success, refreshResponse.getIdentityResponse().getAdvertisingTokenVersion(), refreshResponse.isCstg(), platformType); } } } diff --git a/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java b/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java index 278b4145c..d3f6ad9cf 100644 --- a/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java +++ b/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java @@ -1,6 +1,11 @@ package com.uid2.operator.service; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.FirstLevelHash; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.RawUid; +import com.uid2.operator.util.PrivacyBits; import com.uid2.operator.vertx.ClientInputValidationException; import com.uid2.shared.Const.Data; import com.uid2.shared.encryption.AesCbc; @@ -22,16 +27,16 @@ public EncryptedTokenEncoder(KeyManager keyManager) { this.keyManager = keyManager; } - public byte[] encode(AdvertisingToken t, Instant asOf) { + public byte[] encodeIntoAdvertisingToken(AdvertisingTokenRequest t, Instant asOf) { final KeysetKey masterKey = this.keyManager.getMasterKey(asOf); - final KeysetKey siteEncryptionKey = this.keyManager.getActiveKeyBySiteIdWithFallback(t.publisherIdentity.siteId, Data.AdvertisingTokenSiteId, asOf); + final KeysetKey siteEncryptionKey = this.keyManager.getActiveKeyBySiteIdWithFallback(t.sourcePublisher.siteId, Data.AdvertisingTokenSiteId, asOf); return t.version == TokenVersion.V2 - ? encodeV2(t, masterKey, siteEncryptionKey) - : encodeV3(t, masterKey, siteEncryptionKey); //TokenVersion.V4 also calls encodeV3() since the byte array is identical between V3 and V4 + ? encodeIntoAdvertisingTokenV2(t, masterKey, siteEncryptionKey) + : encodeIntoAdvertisingTokenV3(t, masterKey, siteEncryptionKey); //TokenVersion.V4 also calls encodeV3() since the byte array is identical between V3 and V4 } - private byte[] encodeV2(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) { + private byte[] encodeIntoAdvertisingTokenV2(AdvertisingTokenRequest t, KeysetKey masterKey, KeysetKey siteKey) { final Buffer b = Buffer.buffer(); b.appendByte((byte) t.version.rawVersion); @@ -39,7 +44,7 @@ private byte[] encodeV2(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteK Buffer b2 = Buffer.buffer(); b2.appendLong(t.expiresAt.toEpochMilli()); - encodeSiteIdentityV2(b2, t.publisherIdentity, t.userIdentity, siteKey); + encodeSiteIdentityV2(b2, t.sourcePublisher, t.rawUid, siteKey, t.privacyBits, t.establishedAt); final byte[] encryptedId = AesCbc.encrypt(b2.getBytes(), masterKey).getPayload(); @@ -48,13 +53,15 @@ private byte[] encodeV2(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteK return b.getBytes(); } - private byte[] encodeV3(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) { + private byte[] encodeIntoAdvertisingTokenV3(AdvertisingTokenRequest t, KeysetKey masterKey, KeysetKey siteKey) { final Buffer sitePayload = Buffer.buffer(69); - encodePublisherIdentityV3(sitePayload, t.publisherIdentity); - sitePayload.appendInt(t.userIdentity.privacyBits); - sitePayload.appendLong(t.userIdentity.establishedAt.toEpochMilli()); - sitePayload.appendLong(t.userIdentity.refreshedAt.toEpochMilli()); - sitePayload.appendBytes(t.userIdentity.id); // 32 or 33 bytes + encodePublisherRequesterV3(sitePayload, t.sourcePublisher); + sitePayload.appendInt(t.privacyBits.getAsInt()); + sitePayload.appendLong(t.establishedAt.toEpochMilli()); + // this is the refreshedAt field in the spec - but effectively it is the time this advertising token is generated + // this is a redundant field as it is stored in master payload again, can consider dropping this field in future token version + sitePayload.appendLong(t.createdAt.toEpochMilli()); + sitePayload.appendBytes(t.rawUid.rawUid()); // 32 or 33 bytes final Buffer masterPayload = Buffer.buffer(130); masterPayload.appendLong(t.expiresAt.toEpochMilli()); @@ -64,7 +71,7 @@ private byte[] encodeV3(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteK masterPayload.appendBytes(AesGcm.encrypt(sitePayload.getBytes(), siteKey).getPayload()); final Buffer b = Buffer.buffer(164); - b.appendByte(encodeIdentityTypeV3(t.userIdentity)); + b.appendByte(encodeIdentityTypeV3(t.rawUid.identityScope(), t.rawUid.diiType())); b.appendByte((byte) t.version.rawVersion); b.appendInt(masterKey.getId()); b.appendBytes(AesGcm.encrypt(masterPayload.getBytes(), masterKey).getPayload()); @@ -73,7 +80,7 @@ private byte[] encodeV3(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteK } @Override - public RefreshToken decodeRefreshToken(String s) { + public TokenRefreshRequest decodeRefreshToken(String s) { if (s != null && !s.isEmpty()) { final byte[] bytes; try { @@ -92,7 +99,7 @@ public RefreshToken decodeRefreshToken(String s) { throw new ClientInputValidationException("Invalid refresh token version"); } - private RefreshToken decodeRefreshTokenV2(Buffer b) { + private TokenRefreshRequest decodeRefreshTokenV2(Buffer b) { final Instant createdAt = Instant.ofEpochMilli(b.getLong(1)); //final Instant expiresAt = Instant.ofEpochMilli(b.getLong(9)); final Instant validTill = Instant.ofEpochMilli(b.getLong(17)); @@ -117,17 +124,19 @@ private RefreshToken decodeRefreshTokenV2(Buffer b) { throw new ClientInputValidationException("Failed to decode refreshTokenV2: Identity segment is not valid base64.", e); } - final int privacyBits = b2.getInt(8 + length); + final PrivacyBits privacyBits = PrivacyBits.fromInt(b2.getInt(8 + length)); final long establishedMillis = b2.getLong(8 + length + 4); - return new RefreshToken( + return new TokenRefreshRequest( TokenVersion.V2, createdAt, validTill, new OperatorIdentity(0, OperatorType.Service, 0, 0), - new PublisherIdentity(siteId, 0, 0), - new UserIdentity(IdentityScope.UID2, IdentityType.Email, identity, privacyBits, Instant.ofEpochMilli(establishedMillis), null)); + new SourcePublisher(siteId), + new FirstLevelHash(IdentityScope.UID2, DiiType.Email, identity, + Instant.ofEpochMilli(establishedMillis)), + privacyBits); } - private RefreshToken decodeRefreshTokenV3(Buffer b, byte[] bytes) { + private TokenRefreshRequest decodeRefreshTokenV3(Buffer b, byte[] bytes) { final int keyId = b.getInt(2); final KeysetKey key = this.keyManager.getKey(keyId); @@ -141,27 +150,28 @@ private RefreshToken decodeRefreshTokenV3(Buffer b, byte[] bytes) { final Instant expiresAt = Instant.ofEpochMilli(b2.getLong(0)); final Instant createdAt = Instant.ofEpochMilli(b2.getLong(8)); final OperatorIdentity operatorIdentity = decodeOperatorIdentityV3(b2, 16); - final PublisherIdentity publisherIdentity = decodePublisherIdentityV3(b2, 29); - final int privacyBits = b2.getInt(45); + final SourcePublisher sourcePublisher = decodeSourcePublisherV3(b2, 29); + final PrivacyBits privacyBits = PrivacyBits.fromInt(b2.getInt(45)); final Instant establishedAt = Instant.ofEpochMilli(b2.getLong(49)); final IdentityScope identityScope = decodeIdentityScopeV3(b2.getByte(57)); - final IdentityType identityType = decodeIdentityTypeV3(b2.getByte(57)); - final byte[] id = b2.getBytes(58, 90); + final DiiType diiType = decodeIdentityTypeV3(b2.getByte(57)); + final byte[] firstLevelHash = b2.getBytes(58, 90); if (identityScope != decodeIdentityScopeV3(b.getByte(0))) { throw new ClientInputValidationException("Failed to decode refreshTokenV3: Identity scope mismatch"); } - if (identityType != decodeIdentityTypeV3(b.getByte(0))) { + if (diiType != decodeIdentityTypeV3(b.getByte(0))) { throw new ClientInputValidationException("Failed to decode refreshTokenV3: Identity type mismatch"); } - return new RefreshToken( - TokenVersion.V3, createdAt, expiresAt, operatorIdentity, publisherIdentity, - new UserIdentity(identityScope, identityType, id, privacyBits, establishedAt, null)); + return new TokenRefreshRequest( + TokenVersion.V3, createdAt, expiresAt, operatorIdentity, sourcePublisher, + new FirstLevelHash(identityScope, diiType, firstLevelHash, establishedAt), + privacyBits); } @Override - public AdvertisingToken decodeAdvertisingToken(String base64AdvertisingToken) { + public AdvertisingTokenRequest decodeAdvertisingToken(String base64AdvertisingToken) { //Logic and code copied from: https://github.com/IABTechLab/uid2-client-java/blob/0220ef43c1661ecf3b8f4ed2db524e2db31c06b5/src/main/java/com/uid2/client/Uid2Encryption.java#L37 if (base64AdvertisingToken.length() < 4) { throw new ClientInputValidationException("Advertising token is too short"); @@ -196,7 +206,7 @@ public AdvertisingToken decodeAdvertisingToken(String base64AdvertisingToken) { return decodeAdvertisingTokenV3orV4(b, bytes, tokenVersion); } - public AdvertisingToken decodeAdvertisingTokenV2(Buffer b) { + public AdvertisingTokenRequest decodeAdvertisingTokenV2(Buffer b) { try { final int masterKeyId = b.getInt(1); @@ -214,18 +224,20 @@ public AdvertisingToken decodeAdvertisingTokenV2(Buffer b) { final int siteId = b3.getInt(0); final int length = b3.getInt(4); - final byte[] advertisingId = EncodingUtils.fromBase64(b3.slice(8, 8 + length).getBytes()); + final byte[] rawUid = EncodingUtils.fromBase64(b3.slice(8, 8 + length).getBytes()); - final int privacyBits = b3.getInt(8 + length); + final PrivacyBits privacyBits = PrivacyBits.fromInt(b3.getInt(8 + length)); final long establishedMillis = b3.getLong(8 + length + 4); - return new AdvertisingToken( + return new AdvertisingTokenRequest( TokenVersion.V2, Instant.ofEpochMilli(establishedMillis), Instant.ofEpochMilli(expiresMillis), new OperatorIdentity(0, OperatorType.Service, 0, masterKeyId), - new PublisherIdentity(siteId, siteKeyId, 0), - new UserIdentity(IdentityScope.UID2, IdentityType.Email, advertisingId, privacyBits, Instant.ofEpochMilli(establishedMillis), null) + new SourcePublisher(siteId, siteKeyId, 0), + new RawUid(IdentityScope.UID2, DiiType.Email, rawUid), + privacyBits, + Instant.ofEpochMilli(establishedMillis) ); } catch (Exception e) { @@ -234,7 +246,7 @@ public AdvertisingToken decodeAdvertisingTokenV2(Buffer b) { } - public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, TokenVersion tokenVersion) { + public AdvertisingTokenRequest decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, TokenVersion tokenVersion) { final int masterKeyId = b.getInt(2); final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, this.keyManager.getKey(masterKeyId)); @@ -245,27 +257,29 @@ public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, Tok final int siteKeyId = masterPayload.getInt(29); final Buffer sitePayload = Buffer.buffer(AesGcm.decrypt(masterPayloadBytes, 33, this.keyManager.getKey(siteKeyId))); - final PublisherIdentity publisherIdentity = decodePublisherIdentityV3(sitePayload, 0); - final int privacyBits = sitePayload.getInt(16); + final SourcePublisher sourcePublisher = decodeSourcePublisherV3(sitePayload, 0); + final PrivacyBits privacyBits = PrivacyBits.fromInt(sitePayload.getInt(16)); final Instant establishedAt = Instant.ofEpochMilli(sitePayload.getLong(20)); + // refreshedAt is currently not used final Instant refreshedAt = Instant.ofEpochMilli(sitePayload.getLong(28)); - final byte[] id = sitePayload.slice(36, sitePayload.length()).getBytes(); - final IdentityScope identityScope = id.length == 32 ? IdentityScope.UID2 : decodeIdentityScopeV3(id[0]); - final IdentityType identityType = id.length == 32 ? IdentityType.Email : decodeIdentityTypeV3(id[0]); + final byte[] rawUid = sitePayload.slice(36, sitePayload.length()).getBytes(); + final IdentityScope identityScope = rawUid.length == 32 ? IdentityScope.UID2 : decodeIdentityScopeV3(rawUid[0]); + final DiiType diiType = rawUid.length == 32 ? DiiType.Email : decodeIdentityTypeV3(rawUid[0]); - if (id.length > 32) + if (rawUid.length > 32) { if (identityScope != decodeIdentityScopeV3(b.getByte(0))) { throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity scope mismatch"); } - if (identityType != decodeIdentityTypeV3(b.getByte(0))) { + if (diiType != decodeIdentityTypeV3(b.getByte(0))) { throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity type mismatch"); } } - return new AdvertisingToken( - tokenVersion, createdAt, expiresAt, operatorIdentity, publisherIdentity, - new UserIdentity(identityScope, identityType, id, privacyBits, establishedAt, refreshedAt) + return new AdvertisingTokenRequest( + tokenVersion, createdAt, expiresAt, operatorIdentity, sourcePublisher, + new RawUid(identityScope, diiType, rawUid), + privacyBits, establishedAt ); } @@ -277,22 +291,22 @@ private void recordRefreshTokenVersionCount(String siteId, TokenVersion tokenVer .register(Metrics.globalRegistry).increment(); } - public byte[] encode(RefreshToken t, Instant asOf) { + public byte[] encodeIntoRefreshToken(TokenRefreshRequest t, Instant asOf) { final KeysetKey serviceKey = this.keyManager.getRefreshKey(asOf); switch (t.version) { case V2: - recordRefreshTokenVersionCount(String.valueOf(t.publisherIdentity.siteId), TokenVersion.V2); - return encodeV2(t, serviceKey); + recordRefreshTokenVersionCount(String.valueOf(t.sourcePublisher.siteId), TokenVersion.V2); + return encodeIntoRefreshTokenV2(t, serviceKey); case V3: - recordRefreshTokenVersionCount(String.valueOf(t.publisherIdentity.siteId), TokenVersion.V3); - return encodeV3(t, serviceKey); + recordRefreshTokenVersionCount(String.valueOf(t.sourcePublisher.siteId), TokenVersion.V3); + return encodeIntoRefreshTokenV3(t, serviceKey); default: throw new ClientInputValidationException("RefreshToken version " + t.version + " not supported"); } } - public byte[] encodeV2(RefreshToken t, KeysetKey serviceKey) { + public byte[] encodeIntoRefreshTokenV2(TokenRefreshRequest t, KeysetKey serviceKey) { final Buffer b = Buffer.buffer(); b.appendByte((byte) t.version.rawVersion); b.appendLong(t.createdAt.toEpochMilli()); @@ -300,24 +314,25 @@ public byte[] encodeV2(RefreshToken t, KeysetKey serviceKey) { // give an extra minute for clients which are trying to refresh tokens close to or at the refresh expiry timestamp b.appendLong(t.expiresAt.plusSeconds(60).toEpochMilli()); b.appendInt(serviceKey.getId()); - final byte[] encryptedIdentity = encryptIdentityV2(t.publisherIdentity, t.userIdentity, serviceKey); + final byte[] encryptedIdentity = encryptIdentityV2(t.sourcePublisher, t.firstLevelHash, serviceKey, + t.privacyBits); b.appendBytes(encryptedIdentity); return b.getBytes(); } - public byte[] encodeV3(RefreshToken t, KeysetKey serviceKey) { + public byte[] encodeIntoRefreshTokenV3(TokenRefreshRequest t, KeysetKey serviceKey) { final Buffer refreshPayload = Buffer.buffer(90); refreshPayload.appendLong(t.expiresAt.toEpochMilli()); refreshPayload.appendLong(t.createdAt.toEpochMilli()); encodeOperatorIdentityV3(refreshPayload, t.operatorIdentity); - encodePublisherIdentityV3(refreshPayload, t.publisherIdentity); - refreshPayload.appendInt(t.userIdentity.privacyBits); - refreshPayload.appendLong(t.userIdentity.establishedAt.toEpochMilli()); - refreshPayload.appendByte(encodeIdentityTypeV3(t.userIdentity)); - refreshPayload.appendBytes(t.userIdentity.id); + encodePublisherRequesterV3(refreshPayload, t.sourcePublisher); + refreshPayload.appendInt(t.privacyBits.getAsInt()); + refreshPayload.appendLong(t.firstLevelHash.establishedAt().toEpochMilli()); + refreshPayload.appendByte(encodeIdentityTypeV3(t.firstLevelHash.identityScope(), t.firstLevelHash.diiType())); + refreshPayload.appendBytes(t.firstLevelHash.firstLevelHash()); final Buffer b = Buffer.buffer(124); - b.appendByte(encodeIdentityTypeV3(t.userIdentity)); + b.appendByte(encodeIdentityTypeV3(t.firstLevelHash.identityScope(), t.firstLevelHash.diiType())); b.appendByte((byte) t.version.rawVersion); b.appendInt(serviceKey.getId()); b.appendBytes(AesGcm.encrypt(refreshPayload.getBytes(), serviceKey).getPayload()); @@ -325,9 +340,10 @@ public byte[] encodeV3(RefreshToken t, KeysetKey serviceKey) { return b.getBytes(); } - private void encodeSiteIdentityV2(Buffer b, PublisherIdentity publisherIdentity, UserIdentity userIdentity, KeysetKey siteEncryptionKey) { + private void encodeSiteIdentityV2(Buffer b, SourcePublisher sourcePublisher, RawUid rawUid, + KeysetKey siteEncryptionKey, PrivacyBits privacyBits, Instant establishedAt) { b.appendInt(siteEncryptionKey.getId()); - final byte[] encryptedIdentity = encryptIdentityV2(publisherIdentity, userIdentity, siteEncryptionKey); + final byte[] encryptedIdentity = encryptIdentityV2(sourcePublisher, rawUid, siteEncryptionKey, privacyBits, establishedAt); b.appendBytes(encryptedIdentity); } @@ -337,38 +353,59 @@ public static String bytesToBase64Token(byte[] advertisingTokenBytes, TokenVersi } @Override - public IdentityTokens encode(AdvertisingToken advertisingToken, RefreshToken refreshToken, Instant refreshFrom, Instant asOf) { - - final byte[] advertisingTokenBytes = encode(advertisingToken, asOf); - final String base64AdvertisingToken = bytesToBase64Token(advertisingTokenBytes, advertisingToken.version); - - return new IdentityTokens( - base64AdvertisingToken, - advertisingToken.version, - EncodingUtils.toBase64String(encode(refreshToken, asOf)), - advertisingToken.expiresAt, - refreshToken.expiresAt, + public TokenGenerateResponse encodeIntoIdentityResponse(AdvertisingTokenRequest advertisingTokenRequest, TokenRefreshRequest tokenRefreshRequest, Instant refreshFrom, Instant asOf) { + final String advertisingToken = generateAdvertisingTokenString(advertisingTokenRequest, asOf); + final String refreshToken = generateRefreshTokenString(tokenRefreshRequest, asOf); + return new TokenGenerateResponse( + advertisingToken, + advertisingTokenRequest.version, + refreshToken, + advertisingTokenRequest.expiresAt, + tokenRefreshRequest.expiresAt, refreshFrom ); } - private byte[] encryptIdentityV2(PublisherIdentity publisherIdentity, UserIdentity identity, KeysetKey key) { + private String generateRefreshTokenString(TokenRefreshRequest tokenRefreshRequest, Instant asOf) { + return EncodingUtils.toBase64String(encodeIntoRefreshToken(tokenRefreshRequest, asOf)); + } + + private String generateAdvertisingTokenString(AdvertisingTokenRequest advertisingTokenRequest, Instant asOf) { + final byte[] advertisingTokenBytes = encodeIntoAdvertisingToken(advertisingTokenRequest, asOf); + return bytesToBase64Token(advertisingTokenBytes, advertisingTokenRequest.version); + } + + private byte[] encryptIdentityV2(SourcePublisher sourcePublisher, FirstLevelHash firstLevelHash, + KeysetKey key, PrivacyBits privacyBits) { + return encryptIdentityV2(sourcePublisher, firstLevelHash.firstLevelHash(), privacyBits, + firstLevelHash.establishedAt(), key); + } + + private byte[] encryptIdentityV2(SourcePublisher sourcePublisher, RawUid rawUid, + KeysetKey key, PrivacyBits privacyBits, Instant establishedAt) { + return encryptIdentityV2(sourcePublisher, rawUid.rawUid(), privacyBits, + establishedAt, key); + } + + + private byte[] encryptIdentityV2(SourcePublisher sourcePublisher, byte[] id, PrivacyBits privacyBits, + Instant establishedAt, KeysetKey key) { Buffer b = Buffer.buffer(); try { - b.appendInt(publisherIdentity.siteId); - final byte[] identityBytes = EncodingUtils.toBase64(identity.id); + b.appendInt(sourcePublisher.siteId); + final byte[] identityBytes = EncodingUtils.toBase64(id); b.appendInt(identityBytes.length); b.appendBytes(identityBytes); - b.appendInt(identity.privacyBits); - b.appendLong(identity.establishedAt.toEpochMilli()); + b.appendInt(privacyBits.getAsInt()); + b.appendLong(establishedAt.toEpochMilli()); return AesCbc.encrypt(b.getBytes(), key).getPayload(); } catch (Exception e) { throw new RuntimeException("Could not turn Identity into UTF-8", e); } } - static private byte encodeIdentityTypeV3(UserIdentity userIdentity) { - return (byte) (TokenUtils.encodeIdentityScope(userIdentity.identityScope) | (userIdentity.identityType.value << 2) | 3); + static private byte encodeIdentityTypeV3(IdentityScope identityScope, DiiType diiType) { + return (byte) (TokenUtils.encodeIdentityScope(identityScope) | (diiType.value << 2) | 3); // "| 3" is used so that the 2nd char matches the version when V3 or higher. Eg "3" for V3 and "4" for V4 } @@ -376,18 +413,18 @@ static private IdentityScope decodeIdentityScopeV3(byte value) { return IdentityScope.fromValue((value & 0x10) >> 4); } - static private IdentityType decodeIdentityTypeV3(byte value) { - return IdentityType.fromValue((value & 0xf) >> 2); + static private DiiType decodeIdentityTypeV3(byte value) { + return DiiType.fromValue((value & 0xf) >> 2); } - static void encodePublisherIdentityV3(Buffer b, PublisherIdentity publisherIdentity) { - b.appendInt(publisherIdentity.siteId); - b.appendLong(publisherIdentity.publisherId); - b.appendInt(publisherIdentity.clientKeyId); + static void encodePublisherRequesterV3(Buffer b, SourcePublisher sourcePublisher) { + b.appendInt(sourcePublisher.siteId); + b.appendLong(sourcePublisher.publisherId); + b.appendInt(sourcePublisher.clientKeyId); } - static PublisherIdentity decodePublisherIdentityV3(Buffer b, int offset) { - return new PublisherIdentity(b.getInt(offset), b.getInt(offset + 12), b.getLong(offset + 4)); + static SourcePublisher decodeSourcePublisherV3(Buffer b, int offset) { + return new SourcePublisher(b.getInt(offset), b.getInt(offset + 12), b.getLong(offset + 4)); } static void encodeOperatorIdentityV3(Buffer b, OperatorIdentity operatorIdentity) { diff --git a/src/main/java/com/uid2/operator/service/ITokenEncoder.java b/src/main/java/com/uid2/operator/service/ITokenEncoder.java index 71ab4c5f2..73fe2e70c 100644 --- a/src/main/java/com/uid2/operator/service/ITokenEncoder.java +++ b/src/main/java/com/uid2/operator/service/ITokenEncoder.java @@ -1,15 +1,15 @@ package com.uid2.operator.service; -import com.uid2.operator.model.AdvertisingToken; -import com.uid2.operator.model.IdentityTokens; -import com.uid2.operator.model.RefreshToken; +import com.uid2.operator.model.AdvertisingTokenRequest; +import com.uid2.operator.model.TokenGenerateResponse; +import com.uid2.operator.model.TokenRefreshRequest; import java.time.Instant; public interface ITokenEncoder { - IdentityTokens encode(AdvertisingToken advertisingToken, RefreshToken refreshToken, Instant refreshFrom, Instant asOf); + TokenGenerateResponse encodeIntoIdentityResponse(AdvertisingTokenRequest advertisingTokenRequest, TokenRefreshRequest tokenRefreshRequest, Instant refreshFrom, Instant asOf); - AdvertisingToken decodeAdvertisingToken(String base64String); + AdvertisingTokenRequest decodeAdvertisingToken(String base64String); - RefreshToken decodeRefreshToken(String base64String); + TokenRefreshRequest decodeRefreshToken(String base64String); } diff --git a/src/main/java/com/uid2/operator/service/IUIDOperatorService.java b/src/main/java/com/uid2/operator/service/IUIDOperatorService.java index c64e499fe..c53e66057 100644 --- a/src/main/java/com/uid2/operator/service/IUIDOperatorService.java +++ b/src/main/java/com/uid2/operator/service/IUIDOperatorService.java @@ -1,6 +1,7 @@ package com.uid2.operator.service; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.HashedDii; import com.uid2.shared.model.SaltEntry; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; @@ -11,22 +12,22 @@ public interface IUIDOperatorService { - IdentityTokens generateIdentity(IdentityRequest request); + TokenGenerateResponse generateIdentity(TokenGenerateRequest request); - RefreshResponse refreshIdentity(RefreshToken refreshToken); + TokenRefreshResponse refreshIdentity(TokenRefreshRequest input); - MappedIdentity mapIdentity(MapRequest request); + IdentityMapResponseItem mapHashedDii(IdentityMapRequestItem request); @Deprecated - MappedIdentity map(UserIdentity userIdentity, Instant asOf); + IdentityMapResponseItem map(HashedDii hashedDii, Instant asOf); List getModifiedBuckets(Instant sinceTimestamp); - void invalidateTokensAsync(UserIdentity userIdentity, Instant asOf, Handler> handler); + void invalidateTokensAsync(HashedDii hashedDii, Instant asOf, Handler> handler); - boolean advertisingTokenMatches(String advertisingToken, UserIdentity userIdentity, Instant asOf); + boolean advertisingTokenMatches(String advertisingToken, HashedDii hashedDii, Instant asOf); - Instant getLatestOptoutEntry(UserIdentity userIdentity, Instant asOf); + Instant getLatestOptoutEntry(HashedDii hashedDii, Instant asOf); Duration getIdentityExpiryDuration(); } diff --git a/src/main/java/com/uid2/operator/service/InputUtil.java b/src/main/java/com/uid2/operator/service/InputUtil.java index 839e4e0f3..ab2b573b5 100644 --- a/src/main/java/com/uid2/operator/service/InputUtil.java +++ b/src/main/java/com/uid2/operator/service/InputUtil.java @@ -1,10 +1,8 @@ package com.uid2.operator.service; -import com.uid2.operator.model.IdentityScope; -import com.uid2.operator.model.IdentityType; -import com.uid2.operator.model.UserIdentity; - -import java.time.Instant; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.HashedDii; public class InputUtil { @@ -169,7 +167,7 @@ public static String normalizeEmailString(String email) { return addressPartToUse.append('@').append(domainPart).toString(); } - public enum IdentityInputType { + public enum DiiInputType { Raw, Hash } @@ -185,62 +183,63 @@ private static enum EmailParsingState { public static class InputVal { private final String provided; private final String normalized; - private final IdentityType identityType; - private final IdentityInputType inputType; + //Directly Identifying Information (DII) (email or phone) see https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii + private final DiiType diiType; + private final DiiInputType inputType; private final boolean valid; - private final byte[] identityInput; + private final byte[] diiInput; - public InputVal(String provided, String normalized, IdentityType identityType, IdentityInputType inputType, boolean valid) { + public InputVal(String provided, String normalized, DiiType diiType, DiiInputType inputType, boolean valid) { this.provided = provided; this.normalized = normalized; - this.identityType = identityType; + this.diiType = diiType; this.inputType = inputType; this.valid = valid; if (valid) { - if (this.inputType == IdentityInputType.Raw) { - this.identityInput = TokenUtils.getIdentityHash(this.normalized); + if (this.inputType == DiiInputType.Raw) { + this.diiInput = TokenUtils.getDiiHash(this.normalized); } else { - this.identityInput = EncodingUtils.fromBase64(this.normalized); + this.diiInput = EncodingUtils.fromBase64(this.normalized); } } else { - this.identityInput = null; + this.diiInput = null; } } public static InputVal validEmail(String input, String normalized) { - return new InputVal(input, normalized, IdentityType.Email, IdentityInputType.Raw, true); + return new InputVal(input, normalized, DiiType.Email, DiiInputType.Raw, true); } public static InputVal invalidEmail(String input) { - return new InputVal(input, null, IdentityType.Email, IdentityInputType.Raw, false); + return new InputVal(input, null, DiiType.Email, DiiInputType.Raw, false); } public static InputVal validEmailHash(String input, String normalized) { - return new InputVal(input, normalized, IdentityType.Email, IdentityInputType.Hash, true); + return new InputVal(input, normalized, DiiType.Email, DiiInputType.Hash, true); } public static InputVal invalidEmailHash(String input) { - return new InputVal(input, null, IdentityType.Email, IdentityInputType.Hash, false); + return new InputVal(input, null, DiiType.Email, DiiInputType.Hash, false); } public static InputVal validPhone(String input, String normalized) { - return new InputVal(input, normalized, IdentityType.Phone, IdentityInputType.Raw, true); + return new InputVal(input, normalized, DiiType.Phone, DiiInputType.Raw, true); } public static InputVal invalidPhone(String input) { - return new InputVal(input, null, IdentityType.Phone, IdentityInputType.Raw, false); + return new InputVal(input, null, DiiType.Phone, DiiInputType.Raw, false); } public static InputVal validPhoneHash(String input, String normalized) { - return new InputVal(input, normalized, IdentityType.Phone, IdentityInputType.Hash, true); + return new InputVal(input, normalized, DiiType.Phone, DiiInputType.Hash, true); } public static InputVal invalidPhoneHash(String input) { - return new InputVal(input, null, IdentityType.Phone, IdentityInputType.Hash, false); + return new InputVal(input, null, DiiType.Phone, DiiInputType.Hash, false); } - public byte[] getIdentityInput() { - return this.identityInput; + public byte[] getHashedDiiInput() { + return this.diiInput; } public String getProvided() { @@ -251,24 +250,21 @@ public String getNormalized() { return normalized; } - public IdentityType getIdentityType() { - return identityType; + public DiiType getDiiType() { + return diiType; } - public IdentityInputType getInputType() { return inputType; } + public DiiInputType getInputType() { return inputType; } public boolean isValid() { return valid; } - public UserIdentity toUserIdentity(IdentityScope identityScope, int privacyBits, Instant establishedAt) { - return new UserIdentity( + public HashedDii toHashedDii(IdentityScope identityScope) { + return new HashedDii( identityScope, - this.identityType, - getIdentityInput(), - privacyBits, - establishedAt, - establishedAt); + this.diiType, + getHashedDiiInput()); } } diff --git a/src/main/java/com/uid2/operator/service/TokenUtils.java b/src/main/java/com/uid2/operator/service/TokenUtils.java index 2cabc641b..bf5693014 100644 --- a/src/main/java/com/uid2/operator/service/TokenUtils.java +++ b/src/main/java/com/uid2/operator/service/TokenUtils.java @@ -1,18 +1,18 @@ package com.uid2.operator.service; -import com.uid2.operator.model.IdentityScope; -import com.uid2.operator.model.IdentityType; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.DiiType; import java.util.HashSet; import java.util.Set; public class TokenUtils { - public static byte[] getIdentityHash(String identityString) { + public static byte[] getDiiHash(String identityString) { return EncodingUtils.getSha256Bytes(identityString); } - public static String getIdentityHashString(String identityString) { - return EncodingUtils.toBase64String(getIdentityHash(identityString)); + public static String getDiiHashString(String identityString) { + return EncodingUtils.toBase64String(getDiiHash(identityString)); } public static byte[] getFirstLevelHash(byte[] identityHash, String firstLevelSalt) { @@ -20,46 +20,46 @@ public static byte[] getFirstLevelHash(byte[] identityHash, String firstLevelSal } public static byte[] getFirstLevelHashFromIdentity(String identityString, String firstLevelSalt) { - return getFirstLevelHash(getIdentityHash(identityString), firstLevelSalt); + return getFirstLevelHash(getDiiHash(identityString), firstLevelSalt); } public static byte[] getFirstLevelHashFromIdentityHash(String identityHash, String firstLevelSalt) { return EncodingUtils.getSha256Bytes(identityHash, firstLevelSalt); } - public static byte[] getAdvertisingIdV2(byte[] firstLevelHash, String rotatingSalt) { + public static byte[] getRawUidV2(byte[] firstLevelHash, String rotatingSalt) { return EncodingUtils.getSha256Bytes(EncodingUtils.toBase64String(firstLevelHash), rotatingSalt); } - public static byte[] getAdvertisingIdV2FromIdentity(String identityString, String firstLevelSalt, String rotatingSalt) { - return getAdvertisingIdV2(getFirstLevelHashFromIdentity(identityString, firstLevelSalt), rotatingSalt); + public static byte[] getRawUidV2FromIdentity(String identityString, String firstLevelSalt, String rotatingSalt) { + return getRawUidV2(getFirstLevelHashFromIdentity(identityString, firstLevelSalt), rotatingSalt); } - public static byte[] getAdvertisingIdV2FromIdentityHash(String identityString, String firstLevelSalt, String rotatingSalt) { - return getAdvertisingIdV2(getFirstLevelHashFromIdentityHash(identityString, firstLevelSalt), rotatingSalt); + public static byte[] getRawUidV2FromIdentityHash(String identityString, String firstLevelSalt, String rotatingSalt) { + return getRawUidV2(getFirstLevelHashFromIdentityHash(identityString, firstLevelSalt), rotatingSalt); } - public static byte[] getAdvertisingIdV3(IdentityScope scope, IdentityType type, byte[] firstLevelHash, String rotatingSalt) { + public static byte[] getRawUidV3(IdentityScope scope, DiiType type, byte[] firstLevelHash, String rotatingSalt) { final byte[] sha = EncodingUtils.getSha256Bytes(EncodingUtils.toBase64String(firstLevelHash), rotatingSalt); - final byte[] id = new byte[33]; - id[0] = (byte)(encodeIdentityScope(scope) | encodeIdentityType(type)); - System.arraycopy(sha, 0, id, 1, 32); - return id; + final byte[] rawUid = new byte[33]; + rawUid[0] = (byte)(encodeIdentityScope(scope) | encodeIdentityType(type)); + System.arraycopy(sha, 0, rawUid, 1, 32); + return rawUid; } - public static byte[] getAdvertisingIdV3FromIdentity(IdentityScope scope, IdentityType type, String identityString, String firstLevelSalt, String rotatingSalt) { - return getAdvertisingIdV3(scope, type, getFirstLevelHashFromIdentity(identityString, firstLevelSalt), rotatingSalt); + public static byte[] getRawUidV3FromIdentity(IdentityScope scope, DiiType type, String identityString, String firstLevelSalt, String rotatingSalt) { + return getRawUidV3(scope, type, getFirstLevelHashFromIdentity(identityString, firstLevelSalt), rotatingSalt); } - public static byte[] getAdvertisingIdV3FromIdentityHash(IdentityScope scope, IdentityType type, String identityString, String firstLevelSalt, String rotatingSalt) { - return getAdvertisingIdV3(scope, type, getFirstLevelHashFromIdentityHash(identityString, firstLevelSalt), rotatingSalt); + public static byte[] getRawUidV3FromIdentityHash(IdentityScope scope, DiiType type, String identityString, String firstLevelSalt, String rotatingSalt) { + return getRawUidV3(scope, type, getFirstLevelHashFromIdentityHash(identityString, firstLevelSalt), rotatingSalt); } public static byte encodeIdentityScope(IdentityScope identityScope) { return (byte) (identityScope.value << 4); } - public static byte encodeIdentityType(IdentityType identityType) { - return (byte) (identityType.value << 2); + public static byte encodeIdentityType(DiiType diiType) { + return (byte) (diiType.value << 2); } } diff --git a/src/main/java/com/uid2/operator/service/UIDOperatorService.java b/src/main/java/com/uid2/operator/service/UIDOperatorService.java index 5e66dd70c..c492caf33 100644 --- a/src/main/java/com/uid2/operator/service/UIDOperatorService.java +++ b/src/main/java/com/uid2/operator/service/UIDOperatorService.java @@ -1,6 +1,7 @@ package com.uid2.operator.service; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.*; import com.uid2.operator.util.PrivacyBits; import com.uid2.shared.model.SaltEntry; import com.uid2.operator.store.IOptOutStore; @@ -21,8 +22,7 @@ import java.time.format.DateTimeFormatter; import java.util.*; -import static com.uid2.operator.IdentityConst.*; - +import static com.uid2.operator.model.identities.IdentityConst.*; public class UIDOperatorService implements IUIDOperatorService { public static final String IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS = "identity_token_expires_after_seconds"; public static final String REFRESH_TOKEN_EXPIRES_AFTER_SECONDS = "refresh_token_expires_after_seconds"; @@ -32,15 +32,15 @@ public class UIDOperatorService implements IUIDOperatorService { private static final Instant RefreshCutoff = LocalDateTime.parse("2021-03-08T17:00:00", DateTimeFormatter.ISO_LOCAL_DATE_TIME).toInstant(ZoneOffset.UTC); private final ISaltProvider saltProvider; private final IOptOutStore optOutStore; - private final ITokenEncoder encoder; + private final EncryptedTokenEncoder encoder; private final Clock clock; private final IdentityScope identityScope; - private final UserIdentity testOptOutIdentityForEmail; - private final UserIdentity testOptOutIdentityForPhone; - private final UserIdentity testValidateIdentityForEmail; - private final UserIdentity testValidateIdentityForPhone; - private final UserIdentity testRefreshOptOutIdentityForEmail; - private final UserIdentity testRefreshOptOutIdentityForPhone; + private final FirstLevelHash testOptOutIdentityForEmail; + private final FirstLevelHash testOptOutIdentityForPhone; + private final FirstLevelHash testValidateIdentityForEmail; + private final FirstLevelHash testValidateIdentityForPhone; + private final FirstLevelHash testRefreshOptOutIdentityForEmail; + private final FirstLevelHash testRefreshOptOutIdentityForPhone; private final Duration identityExpiresAfter; private final Duration refreshExpiresAfter; private final Duration refreshIdentityAfter; @@ -52,7 +52,7 @@ public class UIDOperatorService implements IUIDOperatorService { private final Handler saltRetrievalResponseHandler; - public UIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProvider saltProvider, ITokenEncoder encoder, Clock clock, + public UIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProvider saltProvider, EncryptedTokenEncoder encoder, Clock clock, IdentityScope identityScope, Handler saltRetrievalResponseHandler) { this.saltProvider = saltProvider; this.encoder = encoder; @@ -61,18 +61,18 @@ public UIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProv this.identityScope = identityScope; this.saltRetrievalResponseHandler = saltRetrievalResponseHandler; - this.testOptOutIdentityForEmail = getFirstLevelHashIdentity(identityScope, IdentityType.Email, - InputUtil.normalizeEmail(OptOutIdentityForEmail).getIdentityInput(), Instant.now()); - this.testOptOutIdentityForPhone = getFirstLevelHashIdentity(identityScope, IdentityType.Phone, - InputUtil.normalizePhone(OptOutIdentityForPhone).getIdentityInput(), Instant.now()); - this.testValidateIdentityForEmail = getFirstLevelHashIdentity(identityScope, IdentityType.Email, - InputUtil.normalizeEmail(ValidateIdentityForEmail).getIdentityInput(), Instant.now()); - this.testValidateIdentityForPhone = getFirstLevelHashIdentity(identityScope, IdentityType.Phone, - InputUtil.normalizePhone(ValidateIdentityForPhone).getIdentityInput(), Instant.now()); - this.testRefreshOptOutIdentityForEmail = getFirstLevelHashIdentity(identityScope, IdentityType.Email, - InputUtil.normalizeEmail(RefreshOptOutIdentityForEmail).getIdentityInput(), Instant.now()); - this.testRefreshOptOutIdentityForPhone = getFirstLevelHashIdentity(identityScope, IdentityType.Phone, - InputUtil.normalizePhone(RefreshOptOutIdentityForPhone).getIdentityInput(), Instant.now()); + this.testOptOutIdentityForEmail = getFirstLevelHashIdentity(identityScope, DiiType.Email, + InputUtil.normalizeEmail(OptOutIdentityForEmail).getHashedDiiInput(), Instant.now()); + this.testOptOutIdentityForPhone = getFirstLevelHashIdentity(identityScope, DiiType.Phone, + InputUtil.normalizePhone(OptOutIdentityForPhone).getHashedDiiInput(), Instant.now()); + this.testValidateIdentityForEmail = getFirstLevelHashIdentity(identityScope, DiiType.Email, + InputUtil.normalizeEmail(ValidateIdentityForEmail).getHashedDiiInput(), Instant.now()); + this.testValidateIdentityForPhone = getFirstLevelHashIdentity(identityScope, DiiType.Phone, + InputUtil.normalizePhone(ValidateIdentityForPhone).getHashedDiiInput(), Instant.now()); + this.testRefreshOptOutIdentityForEmail = getFirstLevelHashIdentity(identityScope, DiiType.Email, + InputUtil.normalizeEmail(RefreshOptOutIdentityForEmail).getHashedDiiInput(), Instant.now()); + this.testRefreshOptOutIdentityForPhone = getFirstLevelHashIdentity(identityScope, DiiType.Phone, + InputUtil.normalizePhone(RefreshOptOutIdentityForPhone).getHashedDiiInput(), Instant.now()); this.operatorIdentity = new OperatorIdentity(0, OperatorType.Service, 0, 0); @@ -95,74 +95,76 @@ public UIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProv } @Override - public IdentityTokens generateIdentity(IdentityRequest request) { + public TokenGenerateResponse generateIdentity(TokenGenerateRequest request) { final Instant now = EncodingUtils.NowUTCMillis(this.clock); - final byte[] firstLevelHash = getFirstLevelHash(request.userIdentity.id, now); - final UserIdentity firstLevelHashIdentity = new UserIdentity( - request.userIdentity.identityScope, request.userIdentity.identityType, firstLevelHash, request.userIdentity.privacyBits, - request.userIdentity.establishedAt, request.userIdentity.refreshedAt); + final byte[] firstLevelHash = getFirstLevelHash(request.hashedDii.hashedDii(), now); + final FirstLevelHash firstLevelHashIdentity = new FirstLevelHash( + request.hashedDii.identityScope(), request.hashedDii.diiType(), firstLevelHash, + request.establishedAt); if (request.shouldCheckOptOut() && getGlobalOptOutResult(firstLevelHashIdentity, false).isOptedOut()) { - return IdentityTokens.LogoutToken; + return TokenGenerateResponse.OptOutResponse; } else { - return generateIdentity(request.publisherIdentity, firstLevelHashIdentity); + return generateIdentity(request.sourcePublisher, firstLevelHashIdentity, request.privacyBits); } } @Override - public RefreshResponse refreshIdentity(RefreshToken token) { + public TokenRefreshResponse refreshIdentity(TokenRefreshRequest input) { // should not be possible as different scopes should be using different keys, but just in case - if (token.userIdentity.identityScope != this.identityScope) { - return RefreshResponse.Invalid; + if (input.firstLevelHash.identityScope() != this.identityScope) { + return TokenRefreshResponse.Invalid; } - if (token.userIdentity.establishedAt.isBefore(RefreshCutoff)) { - return RefreshResponse.Deprecated; + if (input.firstLevelHash.establishedAt().isBefore(RefreshCutoff)) { + return TokenRefreshResponse.Deprecated; } final Instant now = clock.instant(); - if (token.expiresAt.isBefore(now)) { - return RefreshResponse.Expired; + if (input.expiresAt.isBefore(now)) { + return TokenRefreshResponse.Expired; } - final PrivacyBits privacyBits = PrivacyBits.fromInt(token.userIdentity.privacyBits); - final boolean isCstg = privacyBits.isClientSideTokenGenerated(); + final boolean isCstg = input.privacyBits.isClientSideTokenGenerated(); try { - final GlobalOptoutResult logoutEntry = getGlobalOptOutResult(token.userIdentity, true); + final GlobalOptoutResult logoutEntry = getGlobalOptOutResult(input.firstLevelHash, true); final boolean optedOut = logoutEntry.isOptedOut(); - final Duration durationSinceLastRefresh = Duration.between(token.createdAt, now); + final Duration durationSinceLastRefresh = Duration.between(input.createdAt, now); if (!optedOut) { - IdentityTokens identityTokens = this.generateIdentity(token.publisherIdentity, token.userIdentity); + TokenGenerateResponse tokenGenerateResponse = this.generateIdentity(input.sourcePublisher, + input.firstLevelHash, + input.privacyBits); - return RefreshResponse.createRefreshedResponse(identityTokens, durationSinceLastRefresh, isCstg); + return TokenRefreshResponse.createRefreshedResponse(tokenGenerateResponse, durationSinceLastRefresh, isCstg); } else { - return RefreshResponse.Optout; + return TokenRefreshResponse.Optout; } } catch (KeyManager.NoActiveKeyException e) { - return RefreshResponse.NoActiveKey; + return TokenRefreshResponse.NoActiveKey; } catch (Exception ex) { - return RefreshResponse.Invalid; + return TokenRefreshResponse.Invalid; } } @Override - public MappedIdentity mapIdentity(MapRequest request) { - final UserIdentity firstLevelHashIdentity = getFirstLevelHashIdentity(request.userIdentity, request.asOf); - if (request.shouldCheckOptOut() && getGlobalOptOutResult(firstLevelHashIdentity, false).isOptedOut()) { - return MappedIdentity.LogoutIdentity; + public IdentityMapResponseItem mapHashedDii(IdentityMapRequestItem request) { + final FirstLevelHash firstLevelHash = getFirstLevelHashIdentity(request.hashedDii, + request.asOf); + if (request.shouldCheckOptOut() && getGlobalOptOutResult(firstLevelHash, false).isOptedOut()) { + return IdentityMapResponseItem.OptoutIdentity; } else { - return getAdvertisingId(firstLevelHashIdentity, request.asOf); + return generateRawUid(firstLevelHash, request.asOf); } } @Override - public MappedIdentity map(UserIdentity userIdentity, Instant asOf) { - final UserIdentity firstLevelHashIdentity = getFirstLevelHashIdentity(userIdentity, asOf); - return getAdvertisingId(firstLevelHashIdentity, asOf); + public IdentityMapResponseItem map(HashedDii diiIdentity, Instant asOf) { + final FirstLevelHash firstLevelHash = getFirstLevelHashIdentity(diiIdentity, asOf); + return generateRawUid(firstLevelHash, asOf); } @Override @@ -181,11 +183,11 @@ private ISaltProvider.ISaltSnapshot getSaltProviderSnapshot(Instant asOf) { } @Override - public void invalidateTokensAsync(UserIdentity userIdentity, Instant asOf, Handler> handler) { - final UserIdentity firstLevelHashIdentity = getFirstLevelHashIdentity(userIdentity, asOf); - final MappedIdentity mappedIdentity = getAdvertisingId(firstLevelHashIdentity, asOf); + public void invalidateTokensAsync(HashedDii diiIdentity, Instant asOf, Handler> handler) { + final FirstLevelHash firstLevelHash = getFirstLevelHashIdentity(diiIdentity, asOf); + final IdentityMapResponseItem identityMapResponseItem = generateRawUid(firstLevelHash, asOf); - this.optOutStore.addEntry(firstLevelHashIdentity, mappedIdentity.advertisingId, r -> { + this.optOutStore.addEntry(firstLevelHash, identityMapResponseItem.rawUid, r -> { if (r.succeeded()) { handler.handle(Future.succeededFuture(r.result())); } else { @@ -195,18 +197,18 @@ public void invalidateTokensAsync(UserIdentity userIdentity, Instant asOf, Handl } @Override - public boolean advertisingTokenMatches(String advertisingToken, UserIdentity userIdentity, Instant asOf) { - final UserIdentity firstLevelHashIdentity = getFirstLevelHashIdentity(userIdentity, asOf); - final MappedIdentity mappedIdentity = getAdvertisingId(firstLevelHashIdentity, asOf); + public boolean advertisingTokenMatches(String advertisingToken, HashedDii diiIdentity, Instant asOf) { + final FirstLevelHash firstLevelHash = getFirstLevelHashIdentity(diiIdentity, asOf); + final IdentityMapResponseItem identityMapResponseItem = generateRawUid(firstLevelHash, asOf); - final AdvertisingToken token = this.encoder.decodeAdvertisingToken(advertisingToken); - return Arrays.equals(mappedIdentity.advertisingId, token.userIdentity.id); + final AdvertisingTokenRequest token = this.encoder.decodeAdvertisingToken(advertisingToken); + return Arrays.equals(identityMapResponseItem.rawUid, token.rawUid.rawUid()); } @Override - public Instant getLatestOptoutEntry(UserIdentity userIdentity, Instant asOf) { - final UserIdentity firstLevelHashIdentity = getFirstLevelHashIdentity(userIdentity, asOf); - return this.optOutStore.getLatestEntry(firstLevelHashIdentity); + public Instant getLatestOptoutEntry(HashedDii hashedDii, Instant asOf) { + final FirstLevelHash firstLevelHash = getFirstLevelHashIdentity(hashedDii, asOf); + return this.optOutStore.getLatestEntry(firstLevelHash); } @Override @@ -214,56 +216,66 @@ public Duration getIdentityExpiryDuration() { return this.identityExpiresAfter; } - private UserIdentity getFirstLevelHashIdentity(UserIdentity userIdentity, Instant asOf) { - return getFirstLevelHashIdentity(userIdentity.identityScope, userIdentity.identityType, userIdentity.id, asOf); + private FirstLevelHash getFirstLevelHashIdentity(HashedDii hashedDii, Instant asOf) { + return getFirstLevelHashIdentity(hashedDii.identityScope(), hashedDii.diiType(), hashedDii.hashedDii(), asOf); } - private UserIdentity getFirstLevelHashIdentity(IdentityScope identityScope, IdentityType identityType, byte[] identityHash, Instant asOf) { - final byte[] firstLevelHash = getFirstLevelHash(identityHash, asOf); - return new UserIdentity(identityScope, identityType, firstLevelHash, 0, null, null); + private FirstLevelHash getFirstLevelHashIdentity(IdentityScope identityScope, DiiType diiType, byte[] hashedDii, Instant asOf) { + final byte[] firstLevelHash = getFirstLevelHash(hashedDii, asOf); + return new FirstLevelHash(identityScope, diiType, firstLevelHash, null); } private byte[] getFirstLevelHash(byte[] identityHash, Instant asOf) { return TokenUtils.getFirstLevelHash(identityHash, getSaltProviderSnapshot(asOf).getFirstLevelSalt()); } - private MappedIdentity getAdvertisingId(UserIdentity firstLevelHashIdentity, Instant asOf) { - final SaltEntry rotatingSalt = getSaltProviderSnapshot(asOf).getRotatingSalt(firstLevelHashIdentity.id); + private IdentityMapResponseItem generateRawUid(FirstLevelHash firstLevelHash, Instant asOf) { + final SaltEntry rotatingSalt = getSaltProviderSnapshot(asOf).getRotatingSalt(firstLevelHash.firstLevelHash()); - return new MappedIdentity( + return new IdentityMapResponseItem( this.rawUidV3Enabled - ? TokenUtils.getAdvertisingIdV3(firstLevelHashIdentity.identityScope, firstLevelHashIdentity.identityType, firstLevelHashIdentity.id, rotatingSalt.getSalt()) - : TokenUtils.getAdvertisingIdV2(firstLevelHashIdentity.id, rotatingSalt.getSalt()), + ? TokenUtils.getRawUidV3(firstLevelHash.identityScope(), + firstLevelHash.diiType(), firstLevelHash.firstLevelHash(), rotatingSalt.getSalt()) + : TokenUtils.getRawUidV2(firstLevelHash.firstLevelHash(), rotatingSalt.getSalt()), rotatingSalt.getHashedId()); } - private IdentityTokens generateIdentity(PublisherIdentity publisherIdentity, UserIdentity firstLevelHashIdentity) { + private TokenGenerateResponse generateIdentity(SourcePublisher sourcePublisher, + FirstLevelHash firstLevelHash, PrivacyBits privacyBits) { final Instant nowUtc = EncodingUtils.NowUTCMillis(this.clock); - final MappedIdentity mappedIdentity = getAdvertisingId(firstLevelHashIdentity, nowUtc); - final UserIdentity advertisingIdentity = new UserIdentity(firstLevelHashIdentity.identityScope, firstLevelHashIdentity.identityType, - mappedIdentity.advertisingId, firstLevelHashIdentity.privacyBits, firstLevelHashIdentity.establishedAt, nowUtc); + final IdentityMapResponseItem identityMapResponseItem = generateRawUid(firstLevelHash, nowUtc); + final RawUid rawUid = new RawUid(firstLevelHash.identityScope(), + firstLevelHash.diiType(), + identityMapResponseItem.rawUid); - return this.encoder.encode( - this.createAdvertisingToken(publisherIdentity, advertisingIdentity, nowUtc), - this.createRefreshToken(publisherIdentity, firstLevelHashIdentity, nowUtc), + return this.encoder.encodeIntoIdentityResponse( + this.createAdvertisingTokenRequest(sourcePublisher, rawUid, nowUtc, privacyBits, + firstLevelHash.establishedAt()), + this.createTokenRefreshRequest(sourcePublisher, firstLevelHash, nowUtc, privacyBits), nowUtc.plusMillis(refreshIdentityAfter.toMillis()), nowUtc ); } - private RefreshToken createRefreshToken(PublisherIdentity publisherIdentity, UserIdentity userIdentity, Instant now) { - return new RefreshToken( + private TokenRefreshRequest createTokenRefreshRequest(SourcePublisher sourcePublisher, + FirstLevelHash firstLevelHash, + Instant now, + PrivacyBits privacyBits) { + return new TokenRefreshRequest( this.refreshTokenVersion, now, now.plusMillis(refreshExpiresAfter.toMillis()), this.operatorIdentity, - publisherIdentity, - userIdentity); + sourcePublisher, + firstLevelHash, + privacyBits); } - private AdvertisingToken createAdvertisingToken(PublisherIdentity publisherIdentity, UserIdentity userIdentity, Instant now) { - return new AdvertisingToken(TokenVersion.V4, now, now.plusMillis(identityExpiresAfter.toMillis()), this.operatorIdentity, publisherIdentity, userIdentity); + private AdvertisingTokenRequest createAdvertisingTokenRequest(SourcePublisher sourcePublisher, RawUid rawUidIdentity, + Instant now, PrivacyBits privacyBits, Instant establishedAt) { + return new AdvertisingTokenRequest(TokenVersion.V4, now, now.plusMillis(identityExpiresAfter.toMillis()), this.operatorIdentity, sourcePublisher, rawUidIdentity, + privacyBits, establishedAt); } static protected class GlobalOptoutResult { @@ -287,16 +299,16 @@ public Instant getTime() { } } - private GlobalOptoutResult getGlobalOptOutResult(UserIdentity userIdentity, boolean forRefresh) { - if (forRefresh && (userIdentity.matches(testRefreshOptOutIdentityForEmail) || userIdentity.matches(testRefreshOptOutIdentityForPhone))) { + private GlobalOptoutResult getGlobalOptOutResult(FirstLevelHash firstLevelHash, boolean forRefresh) { + if (forRefresh && (firstLevelHash.matches(testRefreshOptOutIdentityForEmail) || firstLevelHash.matches(testRefreshOptOutIdentityForPhone))) { return new GlobalOptoutResult(Instant.now()); - } else if (userIdentity.matches(testValidateIdentityForEmail) || userIdentity.matches(testValidateIdentityForPhone) - || userIdentity.matches(testRefreshOptOutIdentityForEmail) || userIdentity.matches(testRefreshOptOutIdentityForPhone)) { + } else if (firstLevelHash.matches(testValidateIdentityForEmail) || firstLevelHash.matches(testValidateIdentityForPhone) + || firstLevelHash.matches(testRefreshOptOutIdentityForEmail) || firstLevelHash.matches(testRefreshOptOutIdentityForPhone)) { return new GlobalOptoutResult(null); - } else if (userIdentity.matches(testOptOutIdentityForEmail) || userIdentity.matches(testOptOutIdentityForPhone)) { + } else if (firstLevelHash.matches(testOptOutIdentityForEmail) || firstLevelHash.matches(testOptOutIdentityForPhone)) { return new GlobalOptoutResult(Instant.now()); } - Instant result = this.optOutStore.getLatestEntry(userIdentity); + Instant result = this.optOutStore.getLatestEntry(firstLevelHash); return new GlobalOptoutResult(result); } } diff --git a/src/main/java/com/uid2/operator/service/V2RequestUtil.java b/src/main/java/com/uid2/operator/service/V2RequestUtil.java index cd4983865..a83849e79 100644 --- a/src/main/java/com/uid2/operator/service/V2RequestUtil.java +++ b/src/main/java/com/uid2/operator/service/V2RequestUtil.java @@ -1,8 +1,7 @@ package com.uid2.operator.service; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.identities.IdentityScope; import com.uid2.operator.model.KeyManager; -import com.uid2.operator.vertx.ClientInputValidationException; import com.uid2.shared.IClock; import com.uid2.shared.Utils; import com.uid2.shared.auth.ClientKey; @@ -15,7 +14,6 @@ import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; -import java.time.Clock; import java.time.Duration; import java.time.Instant; diff --git a/src/main/java/com/uid2/operator/store/CloudSyncOptOutStore.java b/src/main/java/com/uid2/operator/store/CloudSyncOptOutStore.java index 1bc882c73..35ccf9a32 100644 --- a/src/main/java/com/uid2/operator/store/CloudSyncOptOutStore.java +++ b/src/main/java/com/uid2/operator/store/CloudSyncOptOutStore.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.uid2.operator.Const; -import com.uid2.operator.model.UserIdentity; +import com.uid2.operator.model.identities.FirstLevelHash; import com.uid2.operator.service.EncodingUtils; import com.uid2.shared.Utils; import com.uid2.shared.cloud.CloudStorageException; @@ -74,8 +74,8 @@ public CloudSyncOptOutStore(Vertx vertx, ICloudStorage fsLocal, JsonObject jsonC } @Override - public Instant getLatestEntry(UserIdentity firstLevelHashIdentity) { - long epochSecond = this.snapshot.get().getOptOutTimestamp(firstLevelHashIdentity.id); + public Instant getLatestEntry(FirstLevelHash firstLevelHash) { + long epochSecond = this.snapshot.get().getOptOutTimestamp(firstLevelHash.firstLevelHash()); Instant instant = epochSecond > 0 ? Instant.ofEpochSecond(epochSecond) : null; return instant; } @@ -86,15 +86,15 @@ public long getOptOutTimestampByAdId(String adId) { } @Override - public void addEntry(UserIdentity firstLevelHashIdentity, byte[] advertisingId, Handler> handler) { + public void addEntry(FirstLevelHash firstLevelHash, byte[] advertisingId, Handler> handler) { if (remoteApiHost == null) { handler.handle(Future.failedFuture("remote api not set")); return; } - this.webClient.get(remoteApiPort, remoteApiHost, remoteApiPath). - addQueryParam("identity_hash", EncodingUtils.toBase64String(firstLevelHashIdentity.id)) - .addQueryParam("advertising_id", EncodingUtils.toBase64String(advertisingId)) + this.webClient.get(remoteApiPort, remoteApiHost, remoteApiPath) + .addQueryParam("identity_hash", EncodingUtils.toBase64String(firstLevelHash.firstLevelHash())) + .addQueryParam("advertising_id", EncodingUtils.toBase64String(advertisingId)) // advertising id aka raw UID .putHeader("Authorization", remoteApiBearerToken) .as(BodyCodec.string()) .send(ar -> { diff --git a/src/main/java/com/uid2/operator/store/IOptOutStore.java b/src/main/java/com/uid2/operator/store/IOptOutStore.java index cadfd239a..09ba19e28 100644 --- a/src/main/java/com/uid2/operator/store/IOptOutStore.java +++ b/src/main/java/com/uid2/operator/store/IOptOutStore.java @@ -1,6 +1,6 @@ package com.uid2.operator.store; -import com.uid2.operator.model.UserIdentity; +import com.uid2.operator.model.identities.FirstLevelHash; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; @@ -9,13 +9,14 @@ public interface IOptOutStore { /** - * Get latest Opt-out record with respect to the UID (hashed identity) - * @param firstLevelHashIdentity UID + * Get latest opt-out record + * + * @param firstLevelHash The first level hash of a DII Hash * @return The timestamp of latest opt-out record. NULL if no record. */ - Instant getLatestEntry(UserIdentity firstLevelHashIdentity); + Instant getLatestEntry(FirstLevelHash firstLevelHash); long getOptOutTimestampByAdId(String adId); - void addEntry(UserIdentity firstLevelHashIdentity, byte[] advertisingId, Handler> handler); + void addEntry(FirstLevelHash firstLevelHash, byte[] advertisingId, Handler> handler); } diff --git a/src/main/java/com/uid2/operator/util/PrivacyBits.java b/src/main/java/com/uid2/operator/util/PrivacyBits.java index f8b7edf6a..0df69d7fd 100644 --- a/src/main/java/com/uid2/operator/util/PrivacyBits.java +++ b/src/main/java/com/uid2/operator/util/PrivacyBits.java @@ -3,6 +3,9 @@ public class PrivacyBits { + // For historical reason this bit is set + public static final PrivacyBits DEFAULT = PrivacyBits.fromInt(1); + private static final int BIT_LEGACY = 0; private static final int BIT_CSTG = 1; private static final int BIT_CSTG_OPTOUT = 2; @@ -16,6 +19,24 @@ public class PrivacyBits { public PrivacyBits() { } + public PrivacyBits(PrivacyBits pb) { + bits = pb.bits; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !obj.getClass().equals(this.getClass())) { + return false; + } + PrivacyBits other = (PrivacyBits)obj; + return this.bits == other.bits; + } + + @Override + public int hashCode() { + return this.bits; + } + public PrivacyBits(int bits) { this.bits = bits; } @@ -37,6 +58,9 @@ public boolean isClientSideTokenOptedOut() { public void setLegacyBit() { setBit(BIT_LEGACY);//unknown why this bit is set in https://github.com/IABTechLab/uid2-operator/blob/dbab58346e367c9d4122ad541ff9632dc37bd410/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java#L534 } + public boolean isLegacyBitSet() { + return isBitSet(BIT_LEGACY); + } private void setBit(int position) { bits |= (1 << position); diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java index 617074b9b..9dbbc3491 100644 --- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java +++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java @@ -2,7 +2,10 @@ import com.uid2.operator.Const; import com.uid2.operator.model.*; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.TokenGenerateResponse; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.HashedDii; import com.uid2.operator.monitoring.IStatsCollectorQueue; import com.uid2.operator.monitoring.StatsCollectorHandler; import com.uid2.operator.monitoring.TokenResponseStatsCollector; @@ -68,7 +71,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import static com.uid2.operator.IdentityConst.*; +import static com.uid2.operator.model.identities.IdentityConst.*; import static com.uid2.operator.service.ResponseUtil.*; import static com.uid2.operator.vertx.Endpoints.*; @@ -90,7 +93,7 @@ public class UIDOperatorVerticle extends AbstractVerticle { private final AuthMiddleware auth; private final ISiteStore siteProvider; private final IClientSideKeypairStore clientSideKeypairProvider; - private final ITokenEncoder encoder; + private final EncryptedTokenEncoder encoder; private final ISaltProvider saltProvider; private final IOptOutStore optOutStore; private final IClientKeyProvider clientKeyProvider; @@ -263,7 +266,7 @@ private Router createRoutesSetup() throws IOException { router.get(V0_IDENTITY_MAP.toString()).handler(auth.handle(this::handleIdentityMap, Role.MAPPER)); router.post(V0_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handle(this::handleIdentityMapBatch, Role.MAPPER)); - // Internal service APIs + // internal API to handle user optout of UID router.get(V0_TOKEN_LOGOUT.toString()).handler(auth.handle(this::handleLogoutAsync, Role.OPTOUT)); // only uncomment to do local testing @@ -292,6 +295,7 @@ private void setupV2Routes(Router mainRouter, BodyHandler bodyHandler) { rc -> v2PayloadHandler.handle(rc, this::handleKeysSharing), Role.SHARER, Role.ID_READER)); mainRouter.post(V2_KEY_BIDSTREAM.toString()).handler(bodyHandler).handler(auth.handleV1( rc -> v2PayloadHandler.handle(rc, this::handleKeysBidstream), Role.ID_READER)); + // internal API to handle user optout of UID mainRouter.post(V2_TOKEN_LOGOUT.toString()).handler(bodyHandler).handler(auth.handleV1( rc -> v2PayloadHandler.handleAsync(rc, this::handleLogoutAsyncV2), Role.OPTOUT)); if (this.optOutStatusApiEnabled) { @@ -465,13 +469,13 @@ else if(emailHash != null) { privacyBits.setLegacyBit(); privacyBits.setClientSideTokenGenerate(); - IdentityTokens identityTokens; + TokenGenerateResponse tokenGenerateResponse; try { - identityTokens = this.idService.generateIdentity( - new IdentityRequest( - new PublisherIdentity(clientSideKeypair.getSiteId(), 0, 0), - input.toUserIdentity(this.identityScope, privacyBits.getAsInt(), Instant.now()), - OptoutCheckPolicy.RespectOptOut)); + tokenGenerateResponse = this.idService.generateIdentity( + new TokenGenerateRequest( + new SourcePublisher(clientSideKeypair.getSiteId()), + input.toHashedDii(this.identityScope), + OptoutCheckPolicy.RespectOptOut, privacyBits, Instant.now())); } catch (KeyManager.NoActiveKeyException e){ SendServerErrorResponseAndRecordStats(rc, "No active encryption key available", clientSideKeypair.getSiteId(), TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2, TokenResponseStatsCollector.ResponseStatus.NoActiveKey, siteProvider, e, platformType); return; @@ -479,12 +483,12 @@ else if(emailHash != null) { JsonObject response; TokenResponseStatsCollector.ResponseStatus responseStatus = TokenResponseStatsCollector.ResponseStatus.Success; - if (identityTokens.isEmptyToken()) { + if (tokenGenerateResponse.isOptedOut()) { response = ResponseUtil.SuccessNoBodyV2(ResponseStatus.OptOut); responseStatus = TokenResponseStatsCollector.ResponseStatus.OptOut; } else { //user not opted out and already generated valid identity token - response = ResponseUtil.SuccessV2(toJsonV1(identityTokens)); + response = ResponseUtil.SuccessV2(tokenGenerateResponse.toJsonV1()); } //if returning an optout token or a successful identity token created originally if (responseStatus == TokenResponseStatsCollector.ResponseStatus.Success) { @@ -492,7 +496,7 @@ else if(emailHash != null) { } final byte[] encryptedResponse = AesGcm.encrypt(response.toBuffer().getBytes(), sharedSecret); rc.response().setStatusCode(200).end(Buffer.buffer(Unpooled.wrappedBuffer(Base64.getEncoder().encode(encryptedResponse)))); - recordTokenResponseStats(clientSideKeypair.getSiteId(), TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2, responseStatus, siteProvider, identityTokens.getAdvertisingTokenVersion(), platformType); + recordTokenResponseStats(clientSideKeypair.getSiteId(), TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2, responseStatus, siteProvider, tokenGenerateResponse.getAdvertisingTokenVersion(), platformType); } private boolean hasValidOriginOrAppName(RoutingContext rc, CstgRequest request, ClientSideKeypair keypair, TokenResponseStatsCollector.PlatformType platformType) { @@ -814,7 +818,7 @@ private void handleTokenRefreshV1(RoutingContext rc) { } try { - final RefreshResponse r = this.refreshIdentity(rc, refreshToken); + final TokenRefreshResponse r = this.refreshIdentity(rc, refreshToken); siteId = rc.get(Const.RoutingContextData.SiteId); if (!r.isRefreshed()) { if (r.isOptOut() || r.isDeprecated()) { @@ -830,7 +834,7 @@ private void handleTokenRefreshV1(RoutingContext rc) { ResponseUtil.LogErrorAndSendResponse(ResponseStatus.UnknownError, 500, rc, "Unknown State"); } } else { - ResponseUtil.Success(rc, toJsonV1(r.getTokens())); + ResponseUtil.Success(rc, r.getIdentityResponse().toJsonV1()); this.recordRefreshDurationStats(siteId, getApiContact(rc), r.getDurationSinceLastRefresh(), rc.request().headers().contains(ORIGIN_HEADER)); } @@ -846,7 +850,7 @@ private void handleTokenRefreshV2(RoutingContext rc) { try { platformType = getPlatformType(rc); String tokenStr = (String) rc.data().get("request"); - final RefreshResponse r = this.refreshIdentity(rc, tokenStr); + final TokenRefreshResponse r = this.refreshIdentity(rc, tokenStr); siteId = rc.get(Const.RoutingContextData.SiteId); if (!r.isRefreshed()) { if (r.isOptOut() || r.isDeprecated()) { @@ -864,7 +868,7 @@ private void handleTokenRefreshV2(RoutingContext rc) { ResponseUtil.LogErrorAndSendResponse(ResponseStatus.UnknownError, 500, rc, "Unknown State"); } } else { - ResponseUtil.SuccessV2(rc, toJsonV1(r.getTokens())); + ResponseUtil.SuccessV2(rc, r.getIdentityResponse().toJsonV1()); this.recordRefreshDurationStats(siteId, getApiContact(rc), r.getDurationSinceLastRefresh(), rc.request().headers().contains(ORIGIN_HEADER)); } TokenResponseStatsCollector.recordRefresh(siteProvider, siteId, TokenResponseStatsCollector.Endpoint.RefreshV2, r, platformType); @@ -879,11 +883,11 @@ private void handleTokenValidateV1(RoutingContext rc) { if (!isTokenInputValid(input, rc)) { return; } - if ((Arrays.equals(ValidateIdentityForEmailHash, input.getIdentityInput()) && input.getIdentityType() == IdentityType.Email) - || (Arrays.equals(ValidateIdentityForPhoneHash, input.getIdentityInput()) && input.getIdentityType() == IdentityType.Phone)) { + if ((Arrays.equals(ValidateIdentityForEmailHash, input.getHashedDiiInput()) && input.getDiiType() == DiiType.Email) + || (Arrays.equals(ValidateIdentityForPhoneHash, input.getHashedDiiInput()) && input.getDiiType() == DiiType.Phone)) { try { final Instant now = Instant.now(); - if (this.idService.advertisingTokenMatches(rc.queryParam("token").get(0), input.toUserIdentity(this.identityScope, 0, now), now)) { + if (this.idService.advertisingTokenMatches(rc.queryParam("token").get(0), input.toHashedDii(this.identityScope), now)) { ResponseUtil.Success(rc, Boolean.TRUE); } else { ResponseUtil.Success(rc, Boolean.FALSE); @@ -910,13 +914,13 @@ private void handleTokenValidateV2(RoutingContext rc) { if (!isTokenInputValid(input, rc)) { return; } - if ((input.getIdentityType() == IdentityType.Email && Arrays.equals(ValidateIdentityForEmailHash, input.getIdentityInput())) - || (input.getIdentityType() == IdentityType.Phone && Arrays.equals(ValidateIdentityForPhoneHash, input.getIdentityInput()))) { + if ((input.getDiiType() == DiiType.Email && Arrays.equals(ValidateIdentityForEmailHash, input.getHashedDiiInput())) + || (input.getDiiType() == DiiType.Phone && Arrays.equals(ValidateIdentityForPhoneHash, input.getHashedDiiInput()))) { try { final Instant now = Instant.now(); final String token = req.getString("token"); - if (this.idService.advertisingTokenMatches(token, input.toUserIdentity(this.identityScope, 0, now), now)) { + if (this.idService.advertisingTokenMatches(token, input.toHashedDii(this.identityScope), now)) { ResponseUtil.SuccessV2(rc, Boolean.TRUE); } else { ResponseUtil.SuccessV2(rc, Boolean.FALSE); @@ -940,14 +944,13 @@ private void handleTokenGenerateV1(RoutingContext rc) { final InputUtil.InputVal input = this.phoneSupport ? this.getTokenInputV1(rc) : this.getTokenInput(rc); platformType = getPlatformType(rc); if (isTokenInputValid(input, rc)) { - final IdentityTokens t = this.idService.generateIdentity( - new IdentityRequest( - new PublisherIdentity(siteId, 0, 0), - input.toUserIdentity(this.identityScope, 1, Instant.now()), + final TokenGenerateResponse response = this.idService.generateIdentity( + new TokenGenerateRequest( + new SourcePublisher(siteId), + input.toHashedDii(this.identityScope), OptoutCheckPolicy.defaultPolicy())); - - ResponseUtil.Success(rc, toJsonV1(t)); - recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV1, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, t.getAdvertisingTokenVersion(), platformType); + ResponseUtil.Success(rc, response.toJsonV1()); + recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV1, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, response.getAdvertisingTokenVersion(), platformType); } } catch (Exception e) { SendServerErrorResponseAndRecordStats(rc, "Unknown error while generating token v1", siteId, TokenResponseStatsCollector.Endpoint.GenerateV1, TokenResponseStatsCollector.ResponseStatus.Unknown, siteProvider, e, platformType); @@ -993,15 +996,15 @@ private void handleTokenGenerateV2(RoutingContext rc) { return; } - final IdentityTokens t = this.idService.generateIdentity( - new IdentityRequest( - new PublisherIdentity(siteId, 0, 0), - input.toUserIdentity(this.identityScope, 1, Instant.now()), + final TokenGenerateResponse response = this.idService.generateIdentity( + new TokenGenerateRequest( + new SourcePublisher(siteId), + input.toHashedDii(this.identityScope), OptoutCheckPolicy.respectOptOut())); - if (t.isEmptyToken()) { + if (response.isOptedOut()) { if (optoutCheckPolicy.getItem1() == OptoutCheckPolicy.DoNotRespect) { // only legacy can use this policy - final InputUtil.InputVal optOutTokenInput = input.getIdentityType() == IdentityType.Email + final InputUtil.InputVal optOutTokenInput = input.getDiiType() == DiiType.Email ? InputUtil.InputVal.validEmail(OptOutTokenIdentityForEmail, OptOutTokenIdentityForEmail) : InputUtil.InputVal.validPhone(OptOutTokenIdentityForPhone, OptOutTokenIdentityForPhone); @@ -1009,21 +1012,21 @@ private void handleTokenGenerateV2(RoutingContext rc) { pb.setLegacyBit(); pb.setClientSideTokenGenerateOptout(); - final IdentityTokens optOutTokens = this.idService.generateIdentity( - new IdentityRequest( - new PublisherIdentity(siteId, 0, 0), - optOutTokenInput.toUserIdentity(this.identityScope, pb.getAsInt(), Instant.now()), - OptoutCheckPolicy.DoNotRespect)); + final TokenGenerateResponse optOutTokens = this.idService.generateIdentity( + new TokenGenerateRequest( + new SourcePublisher(siteId), + optOutTokenInput.toHashedDii(this.identityScope), + OptoutCheckPolicy.DoNotRespect, pb, Instant.now())); - ResponseUtil.SuccessV2(rc, toJsonV1(optOutTokens)); + ResponseUtil.SuccessV2(rc, optOutTokens.toJsonV1()); recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV2, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, optOutTokens.getAdvertisingTokenVersion(), platformType); } else { // new participant, or legacy specified policy/optout_check=1 ResponseUtil.SuccessNoBodyV2("optout", rc); recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV2, TokenResponseStatsCollector.ResponseStatus.OptOut, siteProvider, null, platformType); } } else { - ResponseUtil.SuccessV2(rc, toJsonV1(t)); - recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV2, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, t.getAdvertisingTokenVersion(), platformType); + ResponseUtil.SuccessV2(rc, response.toJsonV1()); + recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV2, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, response.getAdvertisingTokenVersion(), platformType); } } } catch (KeyManager.NoActiveKeyException e) { @@ -1049,14 +1052,14 @@ else if (!input.isValid()) { try { siteId = AuthMiddleware.getAuthClient(rc).getSiteId(); - final IdentityTokens t = this.idService.generateIdentity( - new IdentityRequest( - new PublisherIdentity(siteId, 0, 0), - input.toUserIdentity(this.identityScope, 1, Instant.now()), + final TokenGenerateResponse response = this.idService.generateIdentity( + new TokenGenerateRequest( + new SourcePublisher(siteId), + input.toHashedDii(this.identityScope), OptoutCheckPolicy.defaultPolicy())); - recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV0, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, t.getAdvertisingTokenVersion(), TokenResponseStatsCollector.PlatformType.Other); - sendJsonResponse(rc, toJson(t)); + recordTokenResponseStats(siteId, TokenResponseStatsCollector.Endpoint.GenerateV0, TokenResponseStatsCollector.ResponseStatus.Success, siteProvider, response.getAdvertisingTokenVersion(), TokenResponseStatsCollector.PlatformType.Other); + sendJsonResponse(rc, response.toJsonV0()); } catch (Exception e) { SendServerErrorResponseAndRecordStats(rc, "Unknown error while generating token", siteId, TokenResponseStatsCollector.Endpoint.GenerateV0, TokenResponseStatsCollector.ResponseStatus.Unknown, siteProvider, e, TokenResponseStatsCollector.PlatformType.Other); @@ -1072,9 +1075,9 @@ private void handleTokenRefresh(RoutingContext rc) { } try { - final RefreshResponse r = this.refreshIdentity(rc, tokenList.get(0)); + final TokenRefreshResponse r = this.refreshIdentity(rc, tokenList.get(0)); - sendJsonResponse(rc, toJson(r.getTokens())); + sendJsonResponse(rc, r.getIdentityResponse().toJsonV0()); siteId = rc.get(Const.RoutingContextData.SiteId); if (r.isRefreshed()) { @@ -1089,10 +1092,10 @@ private void handleTokenRefresh(RoutingContext rc) { private void handleValidate(RoutingContext rc) { try { final InputUtil.InputVal input = getTokenInput(rc); - if (input != null && input.isValid() && Arrays.equals(ValidateIdentityForEmailHash, input.getIdentityInput())) { + if (input != null && input.isValid() && Arrays.equals(ValidateIdentityForEmailHash, input.getHashedDiiInput())) { try { final Instant now = Instant.now(); - if (this.idService.advertisingTokenMatches(rc.queryParam("token").get(0), input.toUserIdentity(this.identityScope, 0, now), now)) { + if (this.idService.advertisingTokenMatches(rc.queryParam("token").get(0), input.toHashedDii(this.identityScope), now)) { rc.response().end("true"); } else { rc.response().end("false"); @@ -1113,7 +1116,7 @@ private void handleLogoutAsync(RoutingContext rc) { final InputUtil.InputVal input = this.phoneSupport ? getTokenInputV1(rc) : getTokenInput(rc); if (input.isValid()) { final Instant now = Instant.now(); - this.idService.invalidateTokensAsync(input.toUserIdentity(this.identityScope, 0, now), now, ar -> { + this.idService.invalidateTokensAsync(input.toHashedDii(this.identityScope), now, ar -> { if (ar.succeeded()) { rc.response().end("OK"); } else { @@ -1132,7 +1135,7 @@ private Future handleLogoutAsyncV2(RoutingContext rc) { final Instant now = Instant.now(); Promise promise = Promise.promise(); - this.idService.invalidateTokensAsync(input.toUserIdentity(this.identityScope, 0, now), now, ar -> { + this.idService.invalidateTokensAsync(input.toHashedDii(this.identityScope), now, ar -> { if (ar.succeeded()) { JsonObject body = new JsonObject(); body.put("optout", "OK"); @@ -1154,8 +1157,8 @@ private void handleOptOutGet(RoutingContext rc) { if (input.isValid()) { try { final Instant now = Instant.now(); - final UserIdentity userIdentity = input.toUserIdentity(this.identityScope, 0, now); - final Instant result = this.idService.getLatestOptoutEntry(userIdentity, now); + final HashedDii hashedDii = input.toHashedDii(this.identityScope); + final Instant result = this.idService.getLatestOptoutEntry(hashedDii, now); long timestamp = result == null ? -1 : result.getEpochSecond(); rc.response().setStatusCode(200) .setChunked(true) @@ -1239,11 +1242,11 @@ private void handleIdentityMapV1(RoutingContext rc) { } try { final Instant now = Instant.now(); - final MappedIdentity mappedIdentity = this.idService.map(input.toUserIdentity(this.identityScope, 0, now), now); + final IdentityMapResponseItem identityMapResponseItem = this.idService.map(input.toHashedDii(this.identityScope), now); final JsonObject jsonObject = new JsonObject(); jsonObject.put("identifier", input.getProvided()); - jsonObject.put("advertising_id", EncodingUtils.toBase64String(mappedIdentity.advertisingId)); - jsonObject.put("bucket_id", mappedIdentity.bucketId); + jsonObject.put("advertising_id", EncodingUtils.toBase64String(identityMapResponseItem.rawUid)); + jsonObject.put("bucket_id", identityMapResponseItem.bucketId); ResponseUtil.Success(rc, jsonObject); } catch (Exception e) { ResponseUtil.LogErrorAndSendResponse(ResponseStatus.UnknownError, 500, rc, "Unknown State", e); @@ -1256,8 +1259,8 @@ private void handleIdentityMap(RoutingContext rc) { try { if (isTokenInputValid(input, rc)) { final Instant now = Instant.now(); - final MappedIdentity mappedIdentity = this.idService.map(input.toUserIdentity(this.identityScope, 0, now), now); - rc.response().end(EncodingUtils.toBase64String(mappedIdentity.advertisingId)); + final IdentityMapResponseItem identityMapResponseItem = this.idService.map(input.toHashedDii(this.identityScope), now); + rc.response().end(EncodingUtils.toBase64String(identityMapResponseItem.rawUid)); } } catch (Exception ex) { LOGGER.error("Unexpected error while mapping identity", ex); @@ -1429,16 +1432,16 @@ private InputUtil.InputVal[] getIdentityBulkInputV1(RoutingContext rc) { } if (emails != null && !emails.isEmpty()) { - return createInputListV1(emails, IdentityType.Email, InputUtil.IdentityInputType.Raw); + return createInputListV1(emails, DiiType.Email, InputUtil.DiiInputType.Raw); } else if (emailHashes != null && !emailHashes.isEmpty()) { - return createInputListV1(emailHashes, IdentityType.Email, InputUtil.IdentityInputType.Hash); + return createInputListV1(emailHashes, DiiType.Email, InputUtil.DiiInputType.Hash); } else if (phones != null && !phones.isEmpty()) { - return createInputListV1(phones, IdentityType.Phone, InputUtil.IdentityInputType.Raw); + return createInputListV1(phones, DiiType.Phone, InputUtil.DiiInputType.Raw); } else if (phoneHashes != null && !phoneHashes.isEmpty()) { - return createInputListV1(phoneHashes, IdentityType.Phone, InputUtil.IdentityInputType.Hash); + return createInputListV1(phoneHashes, DiiType.Phone, InputUtil.DiiInputType.Hash); } else { // handle empty array - return createInputListV1(null, IdentityType.Email, InputUtil.IdentityInputType.Raw); + return createInputListV1(null, DiiType.Email, InputUtil.DiiInputType.Raw); } } @@ -1452,13 +1455,13 @@ private JsonObject handleIdentityMapCommon(RoutingContext rc, InputUtil.InputVal for (int i = 0; i < count; ++i) { final InputUtil.InputVal input = inputList[i]; if (input != null && input.isValid()) { - final MappedIdentity mappedIdentity = idService.mapIdentity( - new MapRequest( - input.toUserIdentity(this.identityScope, 0, now), + final IdentityMapResponseItem identityMapResponseItem = idService.mapHashedDii( + new IdentityMapRequestItem( + input.toHashedDii(this.identityScope), OptoutCheckPolicy.respectOptOut(), now)); - if (mappedIdentity.isOptedOut()) { + if (identityMapResponseItem.isOptedOut()) { final JsonObject resp = new JsonObject(); resp.put("identifier", input.getProvided()); resp.put("reason", "optout"); @@ -1467,8 +1470,8 @@ private JsonObject handleIdentityMapCommon(RoutingContext rc, InputUtil.InputVal } else { final JsonObject resp = new JsonObject(); resp.put("identifier", input.getProvided()); - resp.put("advertising_id", EncodingUtils.toBase64String(mappedIdentity.advertisingId)); - resp.put("bucket_id", mappedIdentity.bucketId); + resp.put("advertising_id", EncodingUtils.toBase64String(identityMapResponseItem.rawUid)); + resp.put("bucket_id", identityMapResponseItem.bucketId); mapped.add(resp); } } else { @@ -1530,7 +1533,7 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { Supplier getInputList = null; final JsonArray emails = JsonParseUtils.parseArray(obj, "email", rc); if (emails != null && !emails.isEmpty()) { - getInputList = () -> createInputListV1(emails, IdentityType.Email, InputUtil.IdentityInputType.Raw); + getInputList = () -> createInputListV1(emails, DiiType.Email, InputUtil.DiiInputType.Raw); } final JsonArray emailHashes = JsonParseUtils.parseArray(obj, "email_hash", rc); @@ -1538,7 +1541,7 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { if (getInputList != null) { return null; // only one type of input is allowed } - getInputList = () -> createInputListV1(emailHashes, IdentityType.Email, InputUtil.IdentityInputType.Hash); + getInputList = () -> createInputListV1(emailHashes, DiiType.Email, InputUtil.DiiInputType.Hash); } final JsonArray phones = this.phoneSupport ? JsonParseUtils.parseArray(obj,"phone", rc) : null; @@ -1546,7 +1549,7 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { if (getInputList != null) { return null; // only one type of input is allowed } - getInputList = () -> createInputListV1(phones, IdentityType.Phone, InputUtil.IdentityInputType.Raw); + getInputList = () -> createInputListV1(phones, DiiType.Phone, InputUtil.DiiInputType.Raw); } final JsonArray phoneHashes = this.phoneSupport ? JsonParseUtils.parseArray(obj,"phone_hash", rc) : null; @@ -1554,7 +1557,7 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { if (getInputList != null) { return null; // only one type of input is allowed } - getInputList = () -> createInputListV1(phoneHashes, IdentityType.Phone, InputUtil.IdentityInputType.Hash); + getInputList = () -> createInputListV1(phoneHashes, DiiType.Phone, InputUtil.DiiInputType.Hash); } if (emails == null && emailHashes == null && phones == null && phoneHashes == null) { @@ -1562,7 +1565,7 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { } return getInputList == null ? - createInputListV1(null, IdentityType.Email, InputUtil.IdentityInputType.Raw) : // handle empty array + createInputListV1(null, DiiType.Email, InputUtil.DiiInputType.Raw) : // handle empty array getInputList.get(); } @@ -1766,26 +1769,26 @@ private void recordRefreshTokenVersionCount(String siteId, TokenVersion tokenVer } - private RefreshResponse refreshIdentity(RoutingContext rc, String tokenStr) { - final RefreshToken refreshToken; + private TokenRefreshResponse refreshIdentity(RoutingContext rc, String tokenStr) { + final TokenRefreshRequest tokenRefreshRequest; try { if (AuthMiddleware.isAuthenticated(rc)) { rc.put(Const.RoutingContextData.SiteId, AuthMiddleware.getAuthClient(ClientKey.class, rc).getSiteId()); } - refreshToken = this.encoder.decodeRefreshToken(tokenStr); + tokenRefreshRequest = this.encoder.decodeRefreshToken(tokenStr); } catch (ClientInputValidationException cie) { LOGGER.warn("Failed to decode refresh token for site ID: " + rc.data().get(Const.RoutingContextData.SiteId), cie); - return RefreshResponse.Invalid; + return TokenRefreshResponse.Invalid; } - if (refreshToken == null) { - return RefreshResponse.Invalid; + if (tokenRefreshRequest == null) { + return TokenRefreshResponse.Invalid; } if (!AuthMiddleware.isAuthenticated(rc)) { - rc.put(Const.RoutingContextData.SiteId, refreshToken.publisherIdentity.siteId); + rc.put(Const.RoutingContextData.SiteId, tokenRefreshRequest.sourcePublisher.siteId); } recordRefreshTokenVersionCount(String.valueOf(rc.data().get(Const.RoutingContextData.SiteId)), this.getRefreshTokenVersion(tokenStr)); - return this.idService.refreshIdentity(refreshToken); + return this.idService.refreshIdentity(tokenRefreshRequest); } public static String getSiteName(ISiteStore siteStore, Integer siteId) { @@ -1856,31 +1859,31 @@ private InputUtil.InputVal[] createInputList(JsonArray a, boolean inputAsHash) { } - private InputUtil.InputVal[] createInputListV1(JsonArray a, IdentityType identityType, InputUtil.IdentityInputType inputType) { + private InputUtil.InputVal[] createInputListV1(JsonArray a, DiiType diiType, InputUtil.DiiInputType inputType) { if (a == null || a.isEmpty()) { return new InputUtil.InputVal[0]; } final int size = a.size(); final InputUtil.InputVal[] resp = new InputUtil.InputVal[size]; - if (identityType == IdentityType.Email) { - if (inputType == InputUtil.IdentityInputType.Raw) { + if (diiType == DiiType.Email) { + if (inputType == InputUtil.DiiInputType.Raw) { for (int i = 0; i < size; ++i) { resp[i] = InputUtil.normalizeEmail(a.getString(i)); } - } else if (inputType == InputUtil.IdentityInputType.Hash) { + } else if (inputType == InputUtil.DiiInputType.Hash) { for (int i = 0; i < size; ++i) { resp[i] = InputUtil.normalizeEmailHash(a.getString(i)); } } else { throw new IllegalStateException("inputType"); } - } else if (identityType == IdentityType.Phone) { - if (inputType == InputUtil.IdentityInputType.Raw) { + } else if (diiType == DiiType.Phone) { + if (inputType == InputUtil.DiiInputType.Raw) { for (int i = 0; i < size; ++i) { resp[i] = InputUtil.normalizePhone(a.getString(i)); } - } else if (inputType == InputUtil.IdentityInputType.Hash) { + } else if (inputType == InputUtil.DiiInputType.Hash) { for (int i = 0; i < size; ++i) { resp[i] = InputUtil.normalizePhoneHash(a.getString(i)); } @@ -1986,16 +1989,6 @@ private TransparentConsentParseResult getUserConsentV2(JsonObject req) { } } - private JsonObject toJsonV1(IdentityTokens t) { - final JsonObject json = new JsonObject(); - json.put("advertising_token", t.getAdvertisingToken()); - json.put("refresh_token", t.getRefreshToken()); - json.put("identity_expires", t.getIdentityExpires().toEpochMilli()); - json.put("refresh_expires", t.getRefreshExpires().toEpochMilli()); - json.put("refresh_from", t.getRefreshFrom().toEpochMilli()); - return json; - } - private static MissingAclMode getMissingAclMode(ClientKey clientKey) { return clientKey.hasRole(Role.ID_READER) ? MissingAclMode.ALLOW_ALL : MissingAclMode.DENY_ALL; } @@ -2040,15 +2033,6 @@ private static JsonObject toJson(KeysetKey key) { return json; } - private JsonObject toJson(IdentityTokens t) { - final JsonObject json = new JsonObject(); - json.put("advertisement_token", t.getAdvertisingToken()); - json.put("advertising_token", t.getAdvertisingToken()); - json.put("refresh_token", t.getRefreshToken()); - - return json; - } - private void sendJsonResponse(RoutingContext rc, JsonObject json) { rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json") .end(json.encode()); diff --git a/src/main/java/com/uid2/operator/vertx/V2PayloadHandler.java b/src/main/java/com/uid2/operator/vertx/V2PayloadHandler.java index 10627ab10..dcd388a9c 100644 --- a/src/main/java/com/uid2/operator/vertx/V2PayloadHandler.java +++ b/src/main/java/com/uid2/operator/vertx/V2PayloadHandler.java @@ -1,6 +1,6 @@ package com.uid2.operator.vertx; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.identities.IdentityScope; import com.uid2.operator.model.KeyManager; import com.uid2.operator.monitoring.TokenResponseStatsCollector; import com.uid2.operator.service.EncodingUtils; diff --git a/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java index 7c894fba6..634d8b431 100644 --- a/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java +++ b/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java @@ -3,7 +3,7 @@ import com.uid2.shared.model.TokenVersion; import org.junit.jupiter.api.Test; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.identities.IdentityScope; import com.uid2.shared.auth.Role; import io.vertx.core.Vertx; diff --git a/src/test/java/com/uid2/operator/InputNormalizationTest.java b/src/test/java/com/uid2/operator/InputNormalizationTest.java index adbefbeae..6f825608a 100644 --- a/src/test/java/com/uid2/operator/InputNormalizationTest.java +++ b/src/test/java/com/uid2/operator/InputNormalizationTest.java @@ -71,7 +71,7 @@ public void testValidEmailNormalization() { Assert.assertEquals(normalization.getProvided(), testCase[0]); Assert.assertTrue(normalization.isValid()); Assert.assertEquals(testCase[1], normalization.getNormalized()); - Assert.assertEquals(testCase[2], EncodingUtils.toBase64String(normalization.getIdentityInput())); + Assert.assertEquals(testCase[2], EncodingUtils.toBase64String(normalization.getHashedDiiInput())); } } @@ -90,7 +90,7 @@ public void testValidHashNormalization() { Assert.assertEquals(s, normalization.getProvided()); Assert.assertTrue(normalization.isValid()); Assert.assertEquals(masterHash, normalization.getNormalized()); - Assert.assertEquals(masterHash, EncodingUtils.toBase64String(normalization.getIdentityInput())); + Assert.assertEquals(masterHash, EncodingUtils.toBase64String(normalization.getHashedDiiInput())); } } diff --git a/src/test/java/com/uid2/operator/TokenEncodingTest.java b/src/test/java/com/uid2/operator/TokenEncodingTest.java index 73e11309c..543fd4f21 100644 --- a/src/test/java/com/uid2/operator/TokenEncodingTest.java +++ b/src/test/java/com/uid2/operator/TokenEncodingTest.java @@ -1,9 +1,14 @@ package com.uid2.operator; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.FirstLevelHash; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.RawUid; import com.uid2.operator.service.EncodingUtils; import com.uid2.operator.service.EncryptedTokenEncoder; import com.uid2.operator.service.TokenUtils; +import com.uid2.operator.util.PrivacyBits; import com.uid2.shared.Const.Data; import com.uid2.shared.model.TokenVersion; import com.uid2.shared.store.CloudPath; @@ -53,29 +58,30 @@ public void testRefreshTokenEncoding(TokenVersion tokenVersion) { final byte[] firstLevelHash = TokenUtils.getFirstLevelHashFromIdentity("test@example.com", "some-salt"); - final RefreshToken token = new RefreshToken(tokenVersion, + final TokenRefreshRequest tokenRefreshRequest = new TokenRefreshRequest(tokenVersion, now, now.plusSeconds(360), new OperatorIdentity(101, OperatorType.Service, 102, 103), - new PublisherIdentity(111, 112, 113), - new UserIdentity(IdentityScope.UID2, IdentityType.Email, firstLevelHash, 121, now, now.minusSeconds(122)) + new SourcePublisher(111, 112, 113), + new FirstLevelHash(IdentityScope.UID2, DiiType.Email, firstLevelHash, now), + PrivacyBits.fromInt(121) ); if (tokenVersion == TokenVersion.V4) { - Assert.assertThrows(Exception.class, () -> encoder.encode(token, now)); + Assert.assertThrows(Exception.class, () -> encoder.encodeIntoRefreshToken(tokenRefreshRequest, now)); return; //V4 not supported for RefreshTokens } - final byte[] encodedBytes = encoder.encode(token, now); - final RefreshToken decoded = encoder.decodeRefreshToken(EncodingUtils.toBase64String(encodedBytes)); + final byte[] encodedBytes = encoder.encodeIntoRefreshToken(tokenRefreshRequest, now); + final TokenRefreshRequest decoded = encoder.decodeRefreshToken(EncodingUtils.toBase64String(encodedBytes)); assertEquals(tokenVersion, decoded.version); - assertEquals(token.createdAt, decoded.createdAt); + assertEquals(tokenRefreshRequest.createdAt, decoded.createdAt); int addSeconds = (tokenVersion == TokenVersion.V2) ? 60 : 0; //todo: why is there a 60 second buffer in encodeV2() but not in encodeV3()? - assertEquals(token.expiresAt.plusSeconds(addSeconds), decoded.expiresAt); - assertTrue(token.userIdentity.matches(decoded.userIdentity)); - assertEquals(token.userIdentity.privacyBits, decoded.userIdentity.privacyBits); - assertEquals(token.userIdentity.establishedAt, decoded.userIdentity.establishedAt); - assertEquals(token.publisherIdentity.siteId, decoded.publisherIdentity.siteId); + assertEquals(tokenRefreshRequest.expiresAt.plusSeconds(addSeconds), decoded.expiresAt); + assertTrue(tokenRefreshRequest.firstLevelHash.matches(decoded.firstLevelHash)); + assertEquals(tokenRefreshRequest.privacyBits, decoded.privacyBits); + assertEquals(tokenRefreshRequest.firstLevelHash.establishedAt(), decoded.firstLevelHash.establishedAt()); + assertEquals(tokenRefreshRequest.sourcePublisher.siteId, decoded.sourcePublisher.siteId); Buffer b = Buffer.buffer(encodedBytes); int keyId = b.getInt(tokenVersion == TokenVersion.V2 ? 25 : 2); @@ -100,27 +106,29 @@ public void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTo final EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(this.keyManager); final Instant now = EncodingUtils.NowUTCMillis(); - final byte[] rawUid = UIDOperatorVerticleTest.getRawUid(IdentityType.Email, "test@example.com", IdentityScope.UID2, useRawUIDv3); + final byte[] rawUid = UIDOperatorVerticleTest.getRawUid(DiiType.Email, "test@example.com", IdentityScope.UID2, useRawUIDv3); - final AdvertisingToken token = new AdvertisingToken( + final AdvertisingTokenRequest adTokenRequest = new AdvertisingTokenRequest( adTokenVersion, now, now.plusSeconds(60), new OperatorIdentity(101, OperatorType.Service, 102, 103), - new PublisherIdentity(111, 112, 113), - new UserIdentity(IdentityScope.UID2, IdentityType.Email, rawUid, 121, now, now.minusSeconds(122)) + new SourcePublisher(111, 112, 113), + new RawUid(IdentityScope.UID2, DiiType.Email, rawUid), + PrivacyBits.fromInt(121), + now ); - final byte[] encodedBytes = encoder.encode(token, now); - final AdvertisingToken decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, adTokenVersion)); + final byte[] encodedBytes = encoder.encodeIntoAdvertisingToken(adTokenRequest, now); + final AdvertisingTokenRequest decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, adTokenVersion)); assertEquals(adTokenVersion, decoded.version); - assertEquals(token.createdAt, decoded.createdAt); - assertEquals(token.expiresAt, decoded.expiresAt); - assertTrue(token.userIdentity.matches(decoded.userIdentity)); - assertEquals(token.userIdentity.privacyBits, decoded.userIdentity.privacyBits); - assertEquals(token.userIdentity.establishedAt, decoded.userIdentity.establishedAt); - assertEquals(token.publisherIdentity.siteId, decoded.publisherIdentity.siteId); + assertEquals(adTokenRequest.createdAt, decoded.createdAt); + assertEquals(adTokenRequest.expiresAt, decoded.expiresAt); + assertTrue(adTokenRequest.rawUid.matches(decoded.rawUid)); + assertEquals(adTokenRequest.privacyBits, decoded.privacyBits); + assertEquals(adTokenRequest.establishedAt, decoded.establishedAt); + assertEquals(adTokenRequest.sourcePublisher.siteId, decoded.sourcePublisher.siteId); Buffer b = Buffer.buffer(encodedBytes); int keyId = b.getInt(adTokenVersion == TokenVersion.V2 ? 1 : 2); //TODO - extract master key from token should be a helper function diff --git a/src/test/java/com/uid2/operator/UIDOperatorServiceTest.java b/src/test/java/com/uid2/operator/UIDOperatorServiceTest.java index 37eeef36f..27836857a 100644 --- a/src/test/java/com/uid2/operator/UIDOperatorServiceTest.java +++ b/src/test/java/com/uid2/operator/UIDOperatorServiceTest.java @@ -1,12 +1,14 @@ package com.uid2.operator; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.*; +import com.uid2.operator.service.*; import com.uid2.operator.service.EncodingUtils; import com.uid2.operator.service.EncryptedTokenEncoder; -import com.uid2.operator.service.ITokenEncoder; import com.uid2.operator.service.InputUtil; import com.uid2.operator.service.UIDOperatorService; import com.uid2.operator.store.IOptOutStore; +import com.uid2.operator.util.PrivacyBits; import com.uid2.operator.vertx.OperatorShutdownHandler; import com.uid2.shared.store.CloudPath; import com.uid2.shared.store.ISaltProvider; @@ -21,6 +23,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import static com.uid2.operator.service.TokenUtils.getFirstLevelHash; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.params.ParameterizedTest; @@ -48,13 +52,13 @@ public class UIDOperatorServiceTest { ExtendedUIDOperatorService uid2Service; ExtendedUIDOperatorService euidService; Instant now; - + RotatingSaltProvider saltProvider; final int IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS = 600; final int REFRESH_TOKEN_EXPIRES_AFTER_SECONDS = 900; final int REFRESH_IDENTITY_TOKEN_AFTER_SECONDS = 300; class ExtendedUIDOperatorService extends UIDOperatorService { - public ExtendedUIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProvider saltProvider, ITokenEncoder encoder, Clock clock, IdentityScope identityScope, Handler saltRetrievalResponseHandler) { + public ExtendedUIDOperatorService(JsonObject config, IOptOutStore optOutStore, ISaltProvider saltProvider, EncryptedTokenEncoder encoder, Clock clock, IdentityScope identityScope, Handler saltRetrievalResponseHandler) { super(config, optOutStore, saltProvider, encoder, clock, identityScope, saltRetrievalResponseHandler); } } @@ -75,7 +79,7 @@ void setup() throws Exception { new GlobalScope(new CloudPath("/com.uid2.core/test/keysets/metadata.json"))); keysetProvider.loadContent(); - RotatingSaltProvider saltProvider = new RotatingSaltProvider( + saltProvider = new RotatingSaltProvider( new EmbeddedResourceStorage(Main.class), "/com.uid2.core/test/salts/metadata.json"); saltProvider.loadContent(); @@ -127,107 +131,141 @@ private void setNow(Instant now) { when(clock.instant()).thenAnswer(i -> this.now); } - private UserIdentity createUserIdentity(String rawIdentityHash, IdentityScope scope, IdentityType type) { - return new UserIdentity( + private HashedDii createHashedDiiIdentity(String rawIdentityHash, IdentityScope scope, DiiType type) { + return new HashedDii( scope, type, - rawIdentityHash.getBytes(StandardCharsets.UTF_8), - 0, - this.now.minusSeconds(234), - this.now.plusSeconds(12345) + rawIdentityHash.getBytes(StandardCharsets.UTF_8) ); } - private AdvertisingToken validateAndGetToken(EncryptedTokenEncoder tokenEncoder, String advertisingTokenString, IdentityScope scope, IdentityType type, int siteId) { + private AdvertisingTokenRequest validateAndGetToken(EncryptedTokenEncoder tokenEncoder, String advertisingTokenString, IdentityScope scope, DiiType type, int siteId) { UIDOperatorVerticleTest.validateAdvertisingToken(advertisingTokenString, TokenVersion.V4, scope, type); return tokenEncoder.decodeAdvertisingToken(advertisingTokenString); } - private void assertIdentityScopeIdentityTypeAndEstablishedAt(UserIdentity expctedUserIdentity, UserIdentity actualUserIdentity) { - assertEquals(expctedUserIdentity.identityScope, actualUserIdentity.identityScope); - assertEquals(expctedUserIdentity.identityType, actualUserIdentity.identityType); - assertEquals(expctedUserIdentity.establishedAt, actualUserIdentity.establishedAt); + private void assertIdentityScopeIdentityType(IdentityScope expectedScope, DiiType expectedDiiType, + HashedDii hashedDii) { + assertEquals(expectedScope, hashedDii.identityScope()); + assertEquals(expectedDiiType, hashedDii.diiType()); } + private void assertIdentityScopeIdentityType(IdentityScope expectedScope, DiiType expectedDiiType, + RawUid rawUid) { + assertEquals(expectedScope, rawUid.identityScope()); + assertEquals(expectedDiiType, rawUid.diiType()); + } + + private void assertIdentityScopeIdentityType(IdentityScope expectedScope, DiiType expectedDiiType, + FirstLevelHash firstLevelHash) { + assertEquals(expectedScope, firstLevelHash.identityScope()); + assertEquals(expectedDiiType, firstLevelHash.diiType()); + } + + + + + @ParameterizedTest @CsvSource({"123, V4","127, V4","128, V4"}) public void testGenerateAndRefresh(int siteId, TokenVersion tokenVersion) { - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(siteId, 124, 125), - createUserIdentity("test-email-hash", IdentityScope.UID2, IdentityType.Email), - OptoutCheckPolicy.DoNotRespect + IdentityScope expectedIdentityScope = IdentityScope.UID2; + DiiType expectedDiiType = DiiType.Email; + + + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(siteId, 124, 125), + createHashedDiiIdentity("test-email-hash", expectedIdentityScope, expectedDiiType), + OptoutCheckPolicy.DoNotRespect, PrivacyBits.fromInt(0), + this.now.minusSeconds(234) ); - final IdentityTokens tokens = uid2Service.generateIdentity(identityRequest); + final TokenGenerateResponse tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); + assertNotNull(tokenGenerateResponse); - UIDOperatorVerticleTest.validateAdvertisingToken(tokens.getAdvertisingToken(), tokenVersion, IdentityScope.UID2, IdentityType.Email); - AdvertisingToken advertisingToken = tokenEncoder.decodeAdvertisingToken(tokens.getAdvertisingToken());assertEquals(this.now.plusSeconds(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS), advertisingToken.expiresAt); - assertEquals(identityRequest.publisherIdentity.siteId, advertisingToken.publisherIdentity.siteId); - assertIdentityScopeIdentityTypeAndEstablishedAt(identityRequest.userIdentity, advertisingToken.userIdentity); + UIDOperatorVerticleTest.validateAdvertisingToken(tokenGenerateResponse.getAdvertisingToken(), tokenVersion, IdentityScope.UID2, DiiType.Email); + AdvertisingTokenRequest advertisingTokenRequest = tokenEncoder.decodeAdvertisingToken(tokenGenerateResponse.getAdvertisingToken()); + assertEquals(this.now.plusSeconds(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS), advertisingTokenRequest.expiresAt); + assertEquals(tokenGenerateRequest.sourcePublisher.siteId, advertisingTokenRequest.sourcePublisher.siteId); + assertIdentityScopeIdentityType(expectedIdentityScope, expectedDiiType, + advertisingTokenRequest.rawUid); + assertEquals(tokenGenerateRequest.establishedAt, advertisingTokenRequest.establishedAt); + assertEquals(tokenGenerateRequest.privacyBits, advertisingTokenRequest.privacyBits); + + TokenRefreshRequest tokenRefreshRequest = tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); + assertEquals(this.now, tokenRefreshRequest.createdAt); + assertEquals(this.now.plusSeconds(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS), tokenRefreshRequest.expiresAt); + assertEquals(tokenGenerateRequest.sourcePublisher.siteId, tokenRefreshRequest.sourcePublisher.siteId); + assertIdentityScopeIdentityType(expectedIdentityScope, expectedDiiType, tokenRefreshRequest.firstLevelHash); + assertEquals(tokenGenerateRequest.establishedAt, tokenRefreshRequest.firstLevelHash.establishedAt()); + + final byte[] firstLevelHash = getFirstLevelHash(tokenGenerateRequest.hashedDii.hashedDii(), + saltProvider.getSnapshot(this.now).getFirstLevelSalt() ); + assertArrayEquals(firstLevelHash, tokenRefreshRequest.firstLevelHash.firstLevelHash()); - RefreshToken refreshToken = tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); - assertEquals(this.now, refreshToken.createdAt); - assertEquals(this.now.plusSeconds(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS), refreshToken.expiresAt); - assertEquals(identityRequest.publisherIdentity.siteId, refreshToken.publisherIdentity.siteId); - assertIdentityScopeIdentityTypeAndEstablishedAt(identityRequest.userIdentity, refreshToken.userIdentity); setNow(Instant.now().plusSeconds(200)); reset(shutdownHandler); - final RefreshResponse refreshResponse = uid2Service.refreshIdentity(refreshToken); + final TokenRefreshResponse refreshResponse = uid2Service.refreshIdentity(tokenRefreshRequest); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); assertNotNull(refreshResponse); - assertEquals(RefreshResponse.Status.Refreshed, refreshResponse.getStatus()); - assertNotNull(refreshResponse.getTokens()); - - UIDOperatorVerticleTest.validateAdvertisingToken(refreshResponse.getTokens().getAdvertisingToken(), tokenVersion, IdentityScope.UID2, IdentityType.Email); - AdvertisingToken advertisingToken2 = tokenEncoder.decodeAdvertisingToken(refreshResponse.getTokens().getAdvertisingToken()); - assertEquals(this.now.plusSeconds(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS), advertisingToken2.expiresAt); - assertEquals(advertisingToken.publisherIdentity.siteId, advertisingToken2.publisherIdentity.siteId); - assertIdentityScopeIdentityTypeAndEstablishedAt(advertisingToken.userIdentity, advertisingToken2.userIdentity); - assertArrayEquals(advertisingToken.userIdentity.id, advertisingToken2.userIdentity.id); - - RefreshToken refreshToken2 = tokenEncoder.decodeRefreshToken(refreshResponse.getTokens().getRefreshToken()); - assertEquals(this.now, refreshToken2.createdAt); - assertEquals(this.now.plusSeconds(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS), refreshToken2.expiresAt); - assertEquals(refreshToken.publisherIdentity.siteId, refreshToken2.publisherIdentity.siteId); - assertIdentityScopeIdentityTypeAndEstablishedAt(refreshToken.userIdentity, refreshToken2.userIdentity); - assertArrayEquals(refreshToken.userIdentity.id, refreshToken2.userIdentity.id); + assertEquals(TokenRefreshResponse.Status.Refreshed, refreshResponse.getStatus()); + assertNotNull(refreshResponse.getIdentityResponse()); + + UIDOperatorVerticleTest.validateAdvertisingToken(refreshResponse.getIdentityResponse().getAdvertisingToken(), tokenVersion, IdentityScope.UID2, DiiType.Email); + AdvertisingTokenRequest advertisingTokenRequest2 = tokenEncoder.decodeAdvertisingToken(refreshResponse.getIdentityResponse().getAdvertisingToken()); + assertEquals(this.now.plusSeconds(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS), advertisingTokenRequest2.expiresAt); + assertEquals(advertisingTokenRequest.sourcePublisher.siteId, advertisingTokenRequest2.sourcePublisher.siteId); + assertIdentityScopeIdentityType(expectedIdentityScope, expectedDiiType, + advertisingTokenRequest2.rawUid); + assertEquals(advertisingTokenRequest.establishedAt, advertisingTokenRequest2.establishedAt); + assertArrayEquals(advertisingTokenRequest.rawUid.rawUid(), + advertisingTokenRequest2.rawUid.rawUid()); + assertEquals(tokenGenerateRequest.privacyBits, advertisingTokenRequest2.privacyBits); + + TokenRefreshRequest tokenRefreshRequest2 = tokenEncoder.decodeRefreshToken(refreshResponse.getIdentityResponse().getRefreshToken()); + assertEquals(this.now, tokenRefreshRequest2.createdAt); + assertEquals(this.now.plusSeconds(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS), tokenRefreshRequest2.expiresAt); + assertEquals(tokenRefreshRequest.sourcePublisher.siteId, tokenRefreshRequest2.sourcePublisher.siteId); + assertIdentityScopeIdentityType(expectedIdentityScope, expectedDiiType, tokenRefreshRequest2.firstLevelHash); + assertEquals(tokenRefreshRequest.firstLevelHash.establishedAt(), tokenRefreshRequest2.firstLevelHash.establishedAt()); + assertArrayEquals(tokenRefreshRequest.firstLevelHash.firstLevelHash(), tokenRefreshRequest2.firstLevelHash.firstLevelHash()); + assertArrayEquals(firstLevelHash, tokenRefreshRequest2.firstLevelHash.firstLevelHash()); } @Test public void testTestOptOutKey_DoNotRespectOptout() { final InputUtil.InputVal inputVal = InputUtil.normalizeEmail(IdentityConst.OptOutIdentityForEmail); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(IdentityScope.UID2, 0, this.now), - OptoutCheckPolicy.DoNotRespect + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(IdentityScope.UID2), + OptoutCheckPolicy.DoNotRespect, PrivacyBits.fromInt(0), this.now ); - final IdentityTokens tokens = uid2Service.generateIdentity(identityRequest); + final TokenGenerateResponse tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); - assertFalse(tokens.isEmptyToken()); + assertNotNull(tokenGenerateResponse); + assertFalse(tokenGenerateResponse.isOptedOut()); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); - assertEquals(RefreshResponse.Optout, uid2Service.refreshIdentity(refreshToken)); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); + assertEquals(TokenRefreshResponse.Optout, uid2Service.refreshIdentity(tokenRefreshRequest)); } @Test public void testTestOptOutKey_RespectOptout() { final InputUtil.InputVal inputVal = InputUtil.normalizeEmail(IdentityConst.OptOutIdentityForEmail); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(IdentityScope.UID2, 0, this.now), - OptoutCheckPolicy.RespectOptOut + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(IdentityScope.UID2), + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), this.now ); - final IdentityTokens tokens = uid2Service.generateIdentity(identityRequest); - assertTrue(tokens.isEmptyToken()); + final TokenGenerateResponse tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); + assertTrue(tokenGenerateResponse.isOptedOut()); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); } @@ -237,19 +275,19 @@ public void testTestOptOutKeyIdentityScopeMismatch() { final String email = "optout@example.com"; final InputUtil.InputVal inputVal = InputUtil.normalizeEmail(email); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(IdentityScope.EUID, 0, this.now), - OptoutCheckPolicy.DoNotRespect + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(IdentityScope.EUID), + OptoutCheckPolicy.DoNotRespect, PrivacyBits.fromInt(0), this.now ); - final IdentityTokens tokens = euidService.generateIdentity(identityRequest); + final TokenGenerateResponse tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); + assertNotNull(tokenGenerateResponse); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); reset(shutdownHandler); - assertEquals(RefreshResponse.Invalid, uid2Service.refreshIdentity(refreshToken)); + assertEquals(TokenRefreshResponse.Invalid, uid2Service.refreshIdentity(tokenRefreshRequest)); verify(shutdownHandler, never()).handleSaltRetrievalResponse(anyBoolean()); } @@ -258,49 +296,52 @@ public void testTestOptOutKeyIdentityScopeMismatch() { "Email,test@example.com,EUID", "Phone,+01010101010,UID2", "Phone,+01010101010,EUID"}) - public void testGenerateTokenForOptOutUser(IdentityType type, String identity, IdentityScope scope) { - final UserIdentity userIdentity = createUserIdentity(identity, scope, type); - - final IdentityRequest identityRequestForceGenerate = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - userIdentity, - OptoutCheckPolicy.DoNotRespect); - - final IdentityRequest identityRequestRespectOptOut = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - userIdentity, - OptoutCheckPolicy.RespectOptOut); + public void testGenerateTokenForOptOutUser(DiiType type, String id, IdentityScope scope) { + final HashedDii hashedDii = createHashedDiiIdentity(TokenUtils.getDiiHashString(id), + scope, type); + + final TokenGenerateRequest tokenGenerateRequestForceGenerate = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + hashedDii, + OptoutCheckPolicy.DoNotRespect, PrivacyBits.fromInt(0), + this.now.minusSeconds(234)); + + final TokenGenerateRequest tokenGenerateRequestRespectOptOut = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + hashedDii, + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), + this.now.minusSeconds(234)); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(Instant.now().minus(1, ChronoUnit.HOURS)); - final IdentityTokens tokens; - final AdvertisingToken advertisingToken; - final IdentityTokens tokensAfterOptOut; + final TokenGenerateResponse tokenGenerateResponse; + final AdvertisingTokenRequest advertisingTokenRequest; + final TokenGenerateResponse tokenGenerateResponseAfterOptOut; if (scope == IdentityScope.UID2) { - tokens = uid2Service.generateIdentity(identityRequestForceGenerate); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequestForceGenerate); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - advertisingToken = validateAndGetToken(tokenEncoder, tokens.getAdvertisingToken(), IdentityScope.UID2, userIdentity.identityType, identityRequestRespectOptOut.publisherIdentity.siteId); + advertisingTokenRequest = validateAndGetToken(tokenEncoder, tokenGenerateResponse.getAdvertisingToken(), IdentityScope.UID2, hashedDii.diiType(), tokenGenerateRequestRespectOptOut.sourcePublisher.siteId); reset(shutdownHandler); - tokensAfterOptOut = uid2Service.generateIdentity(identityRequestRespectOptOut); + tokenGenerateResponseAfterOptOut = uid2Service.generateIdentity(tokenGenerateRequestRespectOptOut); } else { - tokens = euidService.generateIdentity(identityRequestForceGenerate); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequestForceGenerate); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - advertisingToken = validateAndGetToken(tokenEncoder, tokens.getAdvertisingToken(), IdentityScope.EUID, userIdentity.identityType, identityRequestRespectOptOut.publisherIdentity.siteId); + advertisingTokenRequest = validateAndGetToken(tokenEncoder, tokenGenerateResponse.getAdvertisingToken(), IdentityScope.EUID, hashedDii.diiType(), tokenGenerateRequestRespectOptOut.sourcePublisher.siteId); reset(shutdownHandler); - tokensAfterOptOut = euidService.generateIdentity(identityRequestRespectOptOut); + tokenGenerateResponseAfterOptOut = euidService.generateIdentity(tokenGenerateRequestRespectOptOut); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); - assertNotNull(advertisingToken.userIdentity); - assertNotNull(tokensAfterOptOut); - assertTrue(tokensAfterOptOut.getAdvertisingToken() == null || tokensAfterOptOut.getAdvertisingToken().isEmpty()); - + assertNotNull(tokenGenerateResponse); + assertNotNull(advertisingTokenRequest.rawUid); + assertNotNull(tokenGenerateResponseAfterOptOut); + assertTrue(tokenGenerateResponseAfterOptOut.getAdvertisingToken() == null || tokenGenerateResponseAfterOptOut.getAdvertisingToken().isEmpty()); + assertTrue(tokenGenerateResponseAfterOptOut.isOptedOut()); } @ParameterizedTest @@ -308,45 +349,45 @@ public void testGenerateTokenForOptOutUser(IdentityType type, String identity, I "Email,test@example.com,EUID", "Phone,+01010101010,UID2", "Phone,+01010101010,EUID"}) - public void testIdentityMapForOptOutUser(IdentityType type, String identity, IdentityScope scope) { - final UserIdentity userIdentity = createUserIdentity(identity, scope, type); + public void testIdentityMapForOptOutUser(DiiType type, String identity, IdentityScope scope) { + final HashedDii hashedDii = createHashedDiiIdentity(identity, scope, type); final Instant now = Instant.now(); - final MapRequest mapRequestForceMap = new MapRequest( - userIdentity, + final IdentityMapRequestItem mapRequestForceIdentityMapItem = new IdentityMapRequestItem( + hashedDii, OptoutCheckPolicy.DoNotRespect, now); - final MapRequest mapRequestRespectOptOut = new MapRequest( - userIdentity, + final IdentityMapRequestItem identityMapRequestItemRespectOptOut = new IdentityMapRequestItem( + hashedDii, OptoutCheckPolicy.RespectOptOut, now); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(Instant.now().minus(1, ChronoUnit.HOURS)); - final MappedIdentity mappedIdentity; - final MappedIdentity mappedIdentityShouldBeOptOut; + final IdentityMapResponseItem identityMapResponseItem; + final IdentityMapResponseItem identityMapResponseItemShouldBeOptOut; if (scope == IdentityScope.UID2) { verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - mappedIdentity = uid2Service.mapIdentity(mapRequestForceMap); + identityMapResponseItem = uid2Service.mapHashedDii(mapRequestForceIdentityMapItem); reset(shutdownHandler); - mappedIdentityShouldBeOptOut = uid2Service.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItemShouldBeOptOut = uid2Service.mapHashedDii(identityMapRequestItemRespectOptOut); } else { verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - mappedIdentity = euidService.mapIdentity(mapRequestForceMap); + identityMapResponseItem = euidService.mapHashedDii(mapRequestForceIdentityMapItem); reset(shutdownHandler); - mappedIdentityShouldBeOptOut = euidService.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItemShouldBeOptOut = euidService.mapHashedDii(identityMapRequestItemRespectOptOut); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(mappedIdentity); - assertFalse(mappedIdentity.isOptedOut()); - assertNotNull(mappedIdentityShouldBeOptOut); - assertTrue(mappedIdentityShouldBeOptOut.isOptedOut()); + assertNotNull(identityMapResponseItem); + assertFalse(identityMapResponseItem.isOptedOut()); + assertNotNull(identityMapResponseItemShouldBeOptOut); + assertTrue(identityMapResponseItemShouldBeOptOut.isOptedOut()); } private enum TestIdentityInputType { @@ -392,25 +433,25 @@ private InputUtil.InputVal generateInputVal(TestIdentityInputType type, String i void testSpecialIdentityOptOutTokenGenerate(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), - OptoutCheckPolicy.RespectOptOut + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), this.now ); // identity has no optout record, ensure generate still returns optout when(this.optOutStore.getLatestEntry(any())).thenReturn(null); - IdentityTokens tokens; + TokenGenerateResponse tokenGenerateResponse; if(scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); } else { - tokens = uid2Service.generateIdentity(identityRequest); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertEquals(tokens, IdentityTokens.LogoutToken); + assertEquals(tokenGenerateResponse, TokenGenerateResponse.OptOutResponse); } @ParameterizedTest @@ -425,25 +466,25 @@ void testSpecialIdentityOptOutTokenGenerate(TestIdentityInputType type, String i void testSpecialIdentityOptOutIdentityMap(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final MapRequest mapRequestRespectOptOut = new MapRequest( - inputVal.toUserIdentity(scope, 0, this.now), + final IdentityMapRequestItem identityMapRequestItemRespectOptOut = new IdentityMapRequestItem( + inputVal.toHashedDii(scope), OptoutCheckPolicy.RespectOptOut, now); // identity has no optout record, ensure map still returns optout when(this.optOutStore.getLatestEntry(any())).thenReturn(null); - final MappedIdentity mappedIdentity; + final IdentityMapResponseItem identityMapResponseItem; if(scope == IdentityScope.EUID) { - mappedIdentity = euidService.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = euidService.mapHashedDii(identityMapRequestItemRespectOptOut); } else { - mappedIdentity = uid2Service.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = uid2Service.mapHashedDii(identityMapRequestItemRespectOptOut); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(mappedIdentity); - assertTrue(mappedIdentity.isOptedOut()); + assertNotNull(identityMapResponseItem); + assertTrue(identityMapResponseItem.isOptedOut()); } @ParameterizedTest @@ -458,30 +499,30 @@ void testSpecialIdentityOptOutIdentityMap(TestIdentityInputType type, String id, void testSpecialIdentityOptOutTokenRefresh(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), - OptoutCheckPolicy.DoNotRespect + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), + OptoutCheckPolicy.DoNotRespect, PrivacyBits.fromInt(0), this.now ); - IdentityTokens tokens; + TokenGenerateResponse tokenGenerateResponse; if(scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); } else { - tokens = uid2Service.generateIdentity(identityRequest); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); - assertNotEquals(IdentityTokens.LogoutToken, tokens); + assertNotNull(tokenGenerateResponse); + assertNotEquals(TokenGenerateResponse.OptOutResponse, tokenGenerateResponse); // identity has no optout record, ensure refresh still returns optout when(this.optOutStore.getLatestEntry(any())).thenReturn(null); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); reset(shutdownHandler); - assertEquals(RefreshResponse.Optout, (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(refreshToken)); + assertEquals(TokenRefreshResponse.Optout, (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(tokenRefreshRequest)); verify(shutdownHandler, never()).handleSaltRetrievalResponse(anyBoolean()); } @@ -497,33 +538,33 @@ void testSpecialIdentityOptOutTokenRefresh(TestIdentityInputType type, String id void testSpecialIdentityRefreshOptOutGenerate(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), - OptoutCheckPolicy.RespectOptOut + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), this.now ); // identity has optout record, ensure still generates when(this.optOutStore.getLatestEntry(any())).thenReturn(Instant.now()); - IdentityTokens tokens; + TokenGenerateResponse tokenGenerateResponse; if(scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); } else { - tokens = uid2Service.generateIdentity(identityRequest); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); - assertNotEquals(IdentityTokens.LogoutToken, tokens); + assertNotNull(tokenGenerateResponse); + assertNotEquals(TokenGenerateResponse.OptOutResponse, tokenGenerateResponse); // identity has no optout record, ensure refresh still returns optout when(this.optOutStore.getLatestEntry(any())).thenReturn(null); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); reset(shutdownHandler); - assertEquals(RefreshResponse.Optout, (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(refreshToken)); + assertEquals(TokenRefreshResponse.Optout, (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(tokenRefreshRequest)); verify(shutdownHandler, never()).handleSaltRetrievalResponse(anyBoolean()); } @@ -539,25 +580,25 @@ void testSpecialIdentityRefreshOptOutGenerate(TestIdentityInputType type, String void testSpecialIdentityRefreshOptOutIdentityMap(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final MapRequest mapRequestRespectOptOut = new MapRequest( - inputVal.toUserIdentity(scope, 0, this.now), + final IdentityMapRequestItem identityMapRequestItemRespectOptOut = new IdentityMapRequestItem( + inputVal.toHashedDii(scope), OptoutCheckPolicy.RespectOptOut, now); // all identities have optout records, ensure refresh-optout identities still map when(this.optOutStore.getLatestEntry(any())).thenReturn(Instant.now()); - final MappedIdentity mappedIdentity; + final IdentityMapResponseItem identityMapResponseItem; if(scope == IdentityScope.EUID) { - mappedIdentity = euidService.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = euidService.mapHashedDii(identityMapRequestItemRespectOptOut); } else { - mappedIdentity = uid2Service.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = uid2Service.mapHashedDii(identityMapRequestItemRespectOptOut); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(mappedIdentity); - assertFalse(mappedIdentity.isOptedOut()); + assertNotNull(identityMapResponseItem); + assertFalse(identityMapResponseItem.isOptedOut()); } @ParameterizedTest @@ -572,29 +613,29 @@ void testSpecialIdentityRefreshOptOutIdentityMap(TestIdentityInputType type, Str void testSpecialIdentityValidateGenerate(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), - OptoutCheckPolicy.RespectOptOut + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), this.now ); // all identities have optout records, ensure validate identities still get generated when(this.optOutStore.getLatestEntry(any())).thenReturn(Instant.now()); - IdentityTokens tokens; - AdvertisingToken advertisingToken; + TokenGenerateResponse tokenGenerateResponse; + AdvertisingTokenRequest advertisingTokenRequest; if (scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); } else { - tokens = uid2Service.generateIdentity(identityRequest); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); } - advertisingToken = validateAndGetToken(tokenEncoder, tokens.getAdvertisingToken(), scope, identityRequest.userIdentity.identityType, identityRequest.publisherIdentity.siteId); + advertisingTokenRequest = validateAndGetToken(tokenEncoder, tokenGenerateResponse.getAdvertisingToken(), scope, tokenGenerateRequest.hashedDii.diiType(), tokenGenerateRequest.sourcePublisher.siteId); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(tokens); - assertNotEquals(IdentityTokens.LogoutToken, tokens); - assertNotNull(advertisingToken.userIdentity); + assertNotNull(tokenGenerateResponse); + assertNotEquals(TokenGenerateResponse.OptOutResponse, tokenGenerateResponse); + assertNotNull(advertisingTokenRequest.rawUid); } @@ -610,25 +651,25 @@ void testSpecialIdentityValidateGenerate(TestIdentityInputType type, String id, void testSpecialIdentityValidateIdentityMap(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final MapRequest mapRequestRespectOptOut = new MapRequest( - inputVal.toUserIdentity(scope, 0, this.now), + final IdentityMapRequestItem identityMapRequestItemRespectOptOut = new IdentityMapRequestItem( + inputVal.toHashedDii(scope), OptoutCheckPolicy.RespectOptOut, now); // all identities have optout records, ensure validate identities still get mapped when(this.optOutStore.getLatestEntry(any())).thenReturn(Instant.now()); - final MappedIdentity mappedIdentity; + final IdentityMapResponseItem identityMapResponseItem; if(scope == IdentityScope.EUID) { - mappedIdentity = euidService.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = euidService.mapHashedDii(identityMapRequestItemRespectOptOut); } else { - mappedIdentity = uid2Service.mapIdentity(mapRequestRespectOptOut); + identityMapResponseItem = uid2Service.mapHashedDii(identityMapRequestItemRespectOptOut); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotNull(mappedIdentity); - assertFalse(mappedIdentity.isOptedOut()); + assertNotNull(identityMapResponseItem); + assertFalse(identityMapResponseItem.isOptedOut()); } @ParameterizedTest @@ -640,28 +681,28 @@ void testSpecialIdentityValidateIdentityMap(TestIdentityInputType type, String i "EmailHash,blah@unifiedid.com,EUID"}) void testNormalIdentityOptIn(TestIdentityInputType type, String id, IdentityScope scope) { InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), OptoutCheckPolicy.DoNotRespect ); - IdentityTokens tokens; + TokenGenerateResponse tokenGenerateResponse; if(scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); } else { - tokens = uid2Service.generateIdentity(identityRequest); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(false); verify(shutdownHandler, never()).handleSaltRetrievalResponse(true); - assertNotEquals(tokens, IdentityTokens.LogoutToken); - assertNotNull(tokens); + assertNotEquals(tokenGenerateResponse, TokenGenerateResponse.OptOutResponse); + assertNotNull(tokenGenerateResponse); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); - RefreshResponse refreshResponse = (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(refreshToken); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); + TokenRefreshResponse refreshResponse = (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(tokenRefreshRequest); assertTrue(refreshResponse.isRefreshed()); - assertNotNull(refreshResponse.getTokens()); - assertNotEquals(RefreshResponse.Optout, refreshResponse); + assertNotNull(refreshResponse.getIdentityResponse()); + assertNotEquals(TokenRefreshResponse.Optout, refreshResponse); } @ParameterizedTest @@ -701,53 +742,53 @@ void testExpiredSaltsNotifiesShutdownHandler(TestIdentityInputType type, String InputUtil.InputVal inputVal = generateInputVal(type, id); - final IdentityRequest identityRequest = new IdentityRequest( - new PublisherIdentity(123, 124, 125), - inputVal.toUserIdentity(scope, 0, this.now), - OptoutCheckPolicy.RespectOptOut); + final TokenGenerateRequest tokenGenerateRequest = new TokenGenerateRequest( + new SourcePublisher(123, 124, 125), + inputVal.toHashedDii(scope), + OptoutCheckPolicy.RespectOptOut, PrivacyBits.fromInt(0), this.now); - IdentityTokens tokens; - AdvertisingToken advertisingToken; + TokenGenerateResponse tokenGenerateResponse; + AdvertisingTokenRequest advertisingTokenRequest; reset(shutdownHandler); if(scope == IdentityScope.EUID) { - tokens = euidService.generateIdentity(identityRequest); - advertisingToken = validateAndGetToken(tokenEncoder, tokens.getAdvertisingToken(), IdentityScope.EUID, identityRequest.userIdentity.identityType, identityRequest.publisherIdentity.siteId); + tokenGenerateResponse = euidService.generateIdentity(tokenGenerateRequest); + advertisingTokenRequest = validateAndGetToken(tokenEncoder, tokenGenerateResponse.getAdvertisingToken(), IdentityScope.EUID, tokenGenerateRequest.hashedDii.diiType(), tokenGenerateRequest.sourcePublisher.siteId); } else { - tokens = uid2Service.generateIdentity(identityRequest); - advertisingToken = validateAndGetToken(tokenEncoder, tokens.getAdvertisingToken(), IdentityScope.UID2, identityRequest.userIdentity.identityType, identityRequest.publisherIdentity.siteId); + tokenGenerateResponse = uid2Service.generateIdentity(tokenGenerateRequest); + advertisingTokenRequest = validateAndGetToken(tokenEncoder, tokenGenerateResponse.getAdvertisingToken(), IdentityScope.UID2, tokenGenerateRequest.hashedDii.diiType(), tokenGenerateRequest.sourcePublisher.siteId); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(true); verify(shutdownHandler, never()).handleSaltRetrievalResponse(false); - assertNotNull(tokens); - assertNotEquals(IdentityTokens.LogoutToken, tokens); - assertNotNull(advertisingToken.userIdentity); + assertNotNull(tokenGenerateResponse); + assertNotEquals(TokenGenerateResponse.OptOutResponse, tokenGenerateResponse); + assertNotNull(advertisingTokenRequest.rawUid); - final RefreshToken refreshToken = this.tokenEncoder.decodeRefreshToken(tokens.getRefreshToken()); + final TokenRefreshRequest tokenRefreshRequest = this.tokenEncoder.decodeRefreshToken(tokenGenerateResponse.getRefreshToken()); reset(shutdownHandler); - RefreshResponse refreshResponse = (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(refreshToken); + TokenRefreshResponse refreshResponse = (scope == IdentityScope.EUID? euidService: uid2Service).refreshIdentity(tokenRefreshRequest); verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(true); verify(shutdownHandler, never()).handleSaltRetrievalResponse(false); assertTrue(refreshResponse.isRefreshed()); - assertNotNull(refreshResponse.getTokens()); - assertNotEquals(RefreshResponse.Optout, refreshResponse); + assertNotNull(refreshResponse.getIdentityResponse()); + assertNotEquals(TokenRefreshResponse.Optout, refreshResponse); - final MapRequest mapRequest = new MapRequest( - inputVal.toUserIdentity(scope, 0, this.now), + final IdentityMapRequestItem identityMapRequestItem = new IdentityMapRequestItem( + inputVal.toHashedDii(scope), OptoutCheckPolicy.RespectOptOut, now); - final MappedIdentity mappedIdentity; + final IdentityMapResponseItem identityMapResponseItem; reset(shutdownHandler); if(scope == IdentityScope.EUID) { - mappedIdentity = euidService.mapIdentity(mapRequest); + identityMapResponseItem = euidService.mapHashedDii(identityMapRequestItem); } else { - mappedIdentity = uid2Service.mapIdentity(mapRequest); + identityMapResponseItem = uid2Service.mapHashedDii(identityMapRequestItem); } verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(true); verify(shutdownHandler, never()).handleSaltRetrievalResponse(false); - assertNotNull(mappedIdentity); - assertFalse(mappedIdentity.isOptedOut()); + assertNotNull(identityMapResponseItem); + assertFalse(identityMapResponseItem.isOptedOut()); } } diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java index 82ab057d0..3c6e45d6b 100644 --- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java +++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java @@ -4,7 +4,9 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import com.uid2.operator.model.*; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.identities.IdentityScope; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.FirstLevelHash; import com.uid2.operator.monitoring.IStatsCollectorQueue; import com.uid2.operator.monitoring.TokenResponseStatsCollector; import com.uid2.operator.service.*; @@ -68,7 +70,7 @@ import java.util.stream.Stream; import static com.uid2.operator.ClientSideTokenGenerateTestUtil.decrypt; -import static com.uid2.operator.IdentityConst.*; +import static com.uid2.operator.model.identities.IdentityConst.*; import static com.uid2.operator.service.EncodingUtils.getSha256; import static com.uid2.operator.vertx.UIDOperatorVerticle.*; import static com.uid2.shared.Const.Data.*; @@ -618,26 +620,26 @@ private void assertTokenStatusMetrics(Integer siteId, TokenResponseStatsCollecto assertEquals(1, actual); } - private byte[] getAdvertisingIdFromIdentity(IdentityType identityType, String identityString, String firstLevelSalt, String rotatingSalt) { - return getRawUid(identityType, identityString, firstLevelSalt, rotatingSalt, getIdentityScope(), useRawUidV3()); + private byte[] getRawUidFromIdentity(DiiType diiType, String identityString, String firstLevelSalt, String rotatingSalt) { + return getRawUid(diiType, identityString, firstLevelSalt, rotatingSalt, getIdentityScope(), useRawUidV3()); } - private static byte[] getRawUid(IdentityType identityType, String identityString, String firstLevelSalt, String rotatingSalt, IdentityScope identityScope, boolean useRawUidV3) { - return !useRawUidV3 - ? TokenUtils.getAdvertisingIdV2FromIdentity(identityString, firstLevelSalt, rotatingSalt) - : TokenUtils.getAdvertisingIdV3FromIdentity(identityScope, identityType, identityString, firstLevelSalt, rotatingSalt); + private static byte[] getRawUid(DiiType diiType, String identityString, String firstLevelSalt, String rotatingSalt, IdentityScope identityScope, boolean useIdentityV3) { + return !useIdentityV3 + ? TokenUtils.getRawUidV2FromIdentity(identityString, firstLevelSalt, rotatingSalt) + : TokenUtils.getRawUidV3FromIdentity(identityScope, diiType, identityString, firstLevelSalt, rotatingSalt); } - public static byte[] getRawUid(IdentityType identityType, String identityString, IdentityScope identityScope, boolean useRawUidV3) { - return !useRawUidV3 - ? TokenUtils.getAdvertisingIdV2FromIdentity(identityString, firstLevelSalt, rotatingSalt123.getSalt()) - : TokenUtils.getAdvertisingIdV3FromIdentity(identityScope, identityType, identityString, firstLevelSalt, rotatingSalt123.getSalt()); + public static byte[] getRawUid(DiiType diiType, String identityString, IdentityScope identityScope, boolean useIdentityV3) { + return !useIdentityV3 + ? TokenUtils.getRawUidV2FromIdentity(identityString, firstLevelSalt, rotatingSalt123.getSalt()) + : TokenUtils.getRawUidV3FromIdentity(identityScope, diiType, identityString, firstLevelSalt, rotatingSalt123.getSalt()); } - private byte[] getAdvertisingIdFromIdentityHash(IdentityType identityType, String identityString, String firstLevelSalt, String rotatingSalt) { + private byte[] getRawUidFromIdentityHash(DiiType diiType, String identityString, String firstLevelSalt, String rotatingSalt) { return !useRawUidV3() - ? TokenUtils.getAdvertisingIdV2FromIdentityHash(identityString, firstLevelSalt, rotatingSalt) - : TokenUtils.getAdvertisingIdV3FromIdentityHash(getIdentityScope(), identityType, identityString, firstLevelSalt, rotatingSalt); + ? TokenUtils.getRawUidV2FromIdentityHash(identityString, firstLevelSalt, rotatingSalt) + : TokenUtils.getRawUidV3FromIdentityHash(getIdentityScope(), diiType, identityString, firstLevelSalt, rotatingSalt); } private JsonObject createBatchEmailsRequestPayload() { @@ -761,7 +763,7 @@ void keyLatestHideRefreshKey(String apiVersion, Vertx vertx, VertxTestContext te void tokenGenerateBothEmailAndHashSpecified(String apiVersion, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String emailAddress = "test@uid2.com"; - final String emailHash = TokenUtils.getIdentityHashString(emailAddress); + final String emailHash = TokenUtils.getDiiHashString(emailAddress); fakeAuth(clientSiteId, Role.GENERATOR); setupSalts(); setupKeys(); @@ -809,28 +811,27 @@ private void assertStatsCollector(String path, String referer, String apiContact assertEquals(siteId, messageItem.getSiteId()); } - private AdvertisingToken validateAndGetToken(EncryptedTokenEncoder encoder, JsonObject body, IdentityType identityType) { //See UID2-79+Token+and+ID+format+v3 + private AdvertisingTokenRequest validateAndGetToken(EncryptedTokenEncoder encoder, JsonObject body, DiiType diiType) { //See UID2-79+Token+and+ID+format+v3 final String advertisingTokenString = body.getString("advertising_token"); - validateAdvertisingToken(advertisingTokenString, getTokenVersion(), getIdentityScope(), identityType); - AdvertisingToken advertisingToken = encoder.decodeAdvertisingToken(advertisingTokenString); - + validateAdvertisingToken(advertisingTokenString, getTokenVersion(), getIdentityScope(), diiType); + AdvertisingTokenRequest advertisingTokenRequest = encoder.decodeAdvertisingToken(advertisingTokenString); // without useIdentityV3() the assert will be trigger as there's no IdentityType in v4 token generated with // a raw UID v2 as old raw UID format doesn't store the identity type (and scope) if (useRawUidV3() && getTokenVersion() == TokenVersion.V4) { - assertEquals(identityType, advertisingToken.userIdentity.identityType); + assertEquals(diiType, advertisingTokenRequest.rawUid.diiType()); } - return advertisingToken; + return advertisingTokenRequest; } - public static void validateAdvertisingToken(String advertisingTokenString, TokenVersion tokenVersion, IdentityScope identityScope, IdentityType identityType) { + public static void validateAdvertisingToken(String advertisingTokenString, TokenVersion tokenVersion, IdentityScope identityScope, DiiType diiType) { if (tokenVersion == TokenVersion.V2) { assertEquals("Ag", advertisingTokenString.substring(0, 2)); } else { String firstChar = advertisingTokenString.substring(0, 1); if (identityScope == IdentityScope.UID2) { - assertEquals(identityType == IdentityType.Email ? "A" : "B", firstChar); + assertEquals(diiType == DiiType.Email ? "A" : "B", firstChar); } else { - assertEquals(identityType == IdentityType.Email ? "E" : "F", firstChar); + assertEquals(diiType == DiiType.Email ? "E" : "F", firstChar); } String secondChar = advertisingTokenString.substring(1, 2); @@ -847,14 +848,11 @@ public static void validateAdvertisingToken(String advertisingTokenString, Token } } - RefreshToken decodeRefreshToken(EncryptedTokenEncoder encoder, String refreshTokenString, IdentityType identityType) { - RefreshToken refreshToken = encoder.decodeRefreshToken(refreshTokenString); - assertEquals(getIdentityScope(), refreshToken.userIdentity.identityScope); - assertEquals(identityType, refreshToken.userIdentity.identityType); - return refreshToken; - } - RefreshToken decodeRefreshToken(EncryptedTokenEncoder encoder, String refreshTokenString) { - return decodeRefreshToken(encoder, refreshTokenString, IdentityType.Email); + TokenRefreshRequest decodeRefreshToken(EncryptedTokenEncoder encoder, String refreshTokenString, DiiType diiType) { + TokenRefreshRequest tokenRefreshRequest = encoder.decodeRefreshToken(refreshTokenString); + assertEquals(getIdentityScope(), tokenRefreshRequest.firstLevelHash.identityScope()); + assertEquals(diiType, tokenRefreshRequest.firstLevelHash.diiType()); + return tokenRefreshRequest; } @ParameterizedTest @@ -866,7 +864,7 @@ void identityMapNewClientNoPolicySpecified(String apiVersion, Vertx vertx, Vertx setupKeys(); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(now.minus(1, ChronoUnit.HOURS)); JsonObject req = new JsonObject(); @@ -893,7 +891,7 @@ void identityMapNewClientWrongPolicySpecified(String apiVersion, String policyPa setupSalts(); setupKeys(); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(now.minus(1, ChronoUnit.HOURS)); JsonObject req = new JsonObject(); JsonArray emails = new JsonArray(); @@ -1127,7 +1125,7 @@ void tokenGenerateNewClientWrongPolicySpecifiedOlderKeySuccessful(String policyP "policy,+01234567890,Phone", "optout_check,someoptout@example.com,Email", "optout_check,+01234567890,Phone"}) - void tokenGenerateOptOutToken(String policyParameterKey, String identity, IdentityType identityType, + void tokenGenerateOptOutToken(String policyParameterKey, String identity, DiiType diiType, Vertx vertx, VertxTestContext testContext) { ClientKey oldClientKey = new ClientKey( null, @@ -1147,13 +1145,13 @@ void tokenGenerateOptOutToken(String policyParameterKey, String identity, Identi setupKeys(); JsonObject v2Payload = new JsonObject(); - v2Payload.put(identityType.name().toLowerCase(), identity); + v2Payload.put(diiType.name().toLowerCase(), identity); v2Payload.put(policyParameterKey, OptoutCheckPolicy.DoNotRespect.policy); sendTokenGenerate("v2", vertx, "", v2Payload, 200, json -> { - InputUtil.InputVal optOutTokenInput = identityType == IdentityType.Email ? + InputUtil.InputVal optOutTokenInput = diiType == DiiType.Email ? InputUtil.InputVal.validEmail(OptOutTokenIdentityForEmail, OptOutTokenIdentityForEmail) : InputUtil.InputVal.validPhone(OptOutIdentityForPhone, OptOutTokenIdentityForPhone); @@ -1166,23 +1164,23 @@ void tokenGenerateOptOutToken(String policyParameterKey, String identity, Identi EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, identityType); - RefreshToken refreshToken = encoder.decodeRefreshToken(body.getString("decrypted_refresh_token")); - final byte[] advertisingId = getAdvertisingIdFromIdentity(identityType, + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, diiType); + TokenRefreshRequest tokenRefreshRequest = encoder.decodeRefreshToken(body.getString("decrypted_refresh_token")); + final byte[] rawUid = getRawUidFromIdentity(diiType, optOutTokenInput.getNormalized(), firstLevelSalt, rotatingSalt123.getSalt()); final byte[] firstLevelHash = TokenUtils.getFirstLevelHashFromIdentity(optOutTokenInput.getNormalized(), firstLevelSalt); - assertArrayEquals(advertisingId, advertisingToken.userIdentity.id); - assertArrayEquals(firstLevelHash, refreshToken.userIdentity.id); + assertArrayEquals(rawUid, advertisingTokenRequest.rawUid.rawUid()); + assertArrayEquals(firstLevelHash, tokenRefreshRequest.firstLevelHash.firstLevelHash()); String advertisingTokenString = body.getString("advertising_token"); final Instant now = Instant.now(); final String token = advertisingTokenString; - final boolean matchedOptedOutIdentity = this.uidOperatorVerticle.getIdService().advertisingTokenMatches(token, optOutTokenInput.toUserIdentity(getIdentityScope(), 0, now), now); + final boolean matchedOptedOutIdentity = this.uidOperatorVerticle.getIdService().advertisingTokenMatches(token, optOutTokenInput.toHashedDii(getIdentityScope()), now); assertTrue(matchedOptedOutIdentity); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertTrue(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); + assertFalse(advertisingTokenRequest.privacyBits.isClientSideTokenGenerated()); + assertTrue(advertisingTokenRequest.privacyBits.isClientSideTokenOptedOut()); assertTokenStatusMetrics( 201, @@ -1226,20 +1224,14 @@ void tokenGenerateForEmail(String apiVersion, Vertx vertx, VertxTestContext test assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email); - - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Email); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Email); - RefreshToken refreshToken = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token")); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), refreshToken.userIdentity.id); - - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_from")), 10); + assertAdvertisingTokenRefreshTokenRequests(advertisingTokenRequest, tokenRefreshRequest, clientSiteId, + getRawUidFromIdentity(DiiType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), + PrivacyBits.DEFAULT, + body, + TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt)); assertStatsCollector("/" + apiVersion + "/token/generate", null, "test-contact", clientSiteId); @@ -1247,11 +1239,32 @@ void tokenGenerateForEmail(String apiVersion, Vertx vertx, VertxTestContext test }); } + public void assertAdvertisingTokenRefreshTokenRequests(AdvertisingTokenRequest advertisingTokenRequest, TokenRefreshRequest tokenRefreshRequest, + int expectedClientSiteId, byte[] expectedRawUidIdentity, PrivacyBits expectedPrivacyBits, JsonObject identityResponse, byte[] firstLevelHashIdentity) { + + assertEquals(expectedClientSiteId, advertisingTokenRequest.sourcePublisher.siteId); + assertEquals(expectedClientSiteId, tokenRefreshRequest.sourcePublisher.siteId); + assertArrayEquals(expectedRawUidIdentity, advertisingTokenRequest.rawUid.rawUid()); + + verifyPrivacyBits(expectedPrivacyBits, advertisingTokenRequest, tokenRefreshRequest); + verifyFirstLevelHashIdentityAndEstablishedAt(firstLevelHashIdentity, tokenRefreshRequest, identityResponse, advertisingTokenRequest.establishedAt); + + assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(identityResponse.getLong("identity_expires")), 10); + assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(identityResponse.getLong("refresh_expires")), 10); + assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(identityResponse.getLong("refresh_from")), 10); + } + + public void verifyPrivacyBits(PrivacyBits expectedValue, AdvertisingTokenRequest advertisingTokenRequest, + TokenRefreshRequest tokenRefreshRequest) { + assertEquals(advertisingTokenRequest.privacyBits, expectedValue); + assertEquals(advertisingTokenRequest.privacyBits, tokenRefreshRequest.privacyBits); + } + @ParameterizedTest @ValueSource(strings = {"v1", "v2"}) void tokenGenerateForEmailHash(String apiVersion, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; - final String emailHash = TokenUtils.getIdentityHashString("test@uid2.com"); + final String emailHash = TokenUtils.getDiiHashString("test@uid2.com"); fakeAuth(clientSiteId, Role.GENERATOR); setupSalts(); setupKeys(); @@ -1268,20 +1281,14 @@ void tokenGenerateForEmailHash(String apiVersion, Vertx vertx, VertxTestContext assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Email); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, apiVersion.equals("v2") ? body.getString("decrypted_refresh_token") : body.getString("refresh_token"), DiiType.Email); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentityHash(IdentityType.Email, emailHash, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); - - RefreshToken refreshToken = decodeRefreshToken(encoder, apiVersion.equals("v2") ? body.getString("decrypted_refresh_token") : body.getString("refresh_token")); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentityHash(emailHash, firstLevelSalt), refreshToken.userIdentity.id); - - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_from")), 10); + assertAdvertisingTokenRefreshTokenRequests(advertisingTokenRequest, tokenRefreshRequest, clientSiteId, + getRawUidFromIdentityHash(DiiType.Email, emailHash, firstLevelSalt, rotatingSalt123.getSalt()), + PrivacyBits.DEFAULT, + body, + TokenUtils.getFirstLevelHashFromIdentityHash(emailHash, firstLevelSalt)); testContext.completeNow(); }); @@ -1302,32 +1309,65 @@ void tokenGenerateThenRefresh(String apiVersion, Vertx vertx, VertxTestContext t assertNotNull(bodyJson); String genRefreshToken = bodyJson.getString("refresh_token"); + EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); + + AdvertisingTokenRequest firstAdvertisingTokenRequest = validateAndGetToken(encoder, bodyJson, + DiiType.Email); + + TokenRefreshRequest firstTokenRefreshRequest = decodeRefreshToken(encoder, bodyJson.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Email); + + assertEquals(firstAdvertisingTokenRequest.establishedAt, firstTokenRefreshRequest.firstLevelHash.establishedAt()); when(this.optOutStore.getLatestEntry(any())).thenReturn(null); + + byte[] expectedRawUidIdentity = getRawUidFromIdentity(DiiType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()); + byte[] expectedFirstLevelHashIdentity = TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt); + + assertAdvertisingTokenRefreshTokenRequests(firstAdvertisingTokenRequest, firstTokenRefreshRequest, clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + + sendTokenRefresh(apiVersion, vertx, ClientVersionHeader, iosClientVersionHeaderValue, testContext, genRefreshToken, bodyJson.getString("refresh_response_key"), 200, refreshRespJson -> { assertEquals("success", refreshRespJson.getString("status")); JsonObject refreshBody = refreshRespJson.getJsonObject("body"); assertNotNull(refreshBody); - EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - - AdvertisingToken advertisingToken = validateAndGetToken(encoder, refreshBody, IdentityType.Email); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, refreshBody, DiiType.Email); String refreshTokenStringNew = refreshBody.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"); assertNotEquals(genRefreshToken, refreshTokenStringNew); - RefreshToken refreshToken = decodeRefreshToken(encoder, refreshTokenStringNew); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), refreshToken.userIdentity.id); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, refreshTokenStringNew, DiiType.Email); - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_from")), 10); + // assert if the ad/refresh tokens from original token/generate is same as the ad/refresh tokens from token/refresh + assertAdvertisingTokenRefreshTokenRequests( + advertisingTokenRequest, + firstTokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + assertAdvertisingTokenRefreshTokenRequests( + firstAdvertisingTokenRequest, + tokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + assertAdvertisingTokenRefreshTokenRequests( + advertisingTokenRequest, + tokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); assertTokenStatusMetrics( clientSiteId, @@ -1371,18 +1411,18 @@ void tokenGenerateThenRefreshSaltsExpired(String apiVersion, Vertx vertx, VertxT assertNotNull(refreshBody); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, refreshBody, IdentityType.Email); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, refreshBody, DiiType.Email); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + assertFalse(advertisingTokenRequest.privacyBits.isClientSideTokenGenerated()); + assertFalse(advertisingTokenRequest.privacyBits.isClientSideTokenOptedOut()); + assertEquals(clientSiteId, advertisingTokenRequest.sourcePublisher.siteId); + assertArrayEquals(getRawUidFromIdentity(DiiType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingTokenRequest.rawUid.rawUid()); String refreshTokenStringNew = refreshBody.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"); assertNotEquals(genRefreshToken, refreshTokenStringNew); - RefreshToken refreshToken = decodeRefreshToken(encoder, refreshTokenStringNew); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), refreshToken.userIdentity.id); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, refreshTokenStringNew, DiiType.Email); + assertEquals(clientSiteId, tokenRefreshRequest.sourcePublisher.siteId); + assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), tokenRefreshRequest.firstLevelHash.firstLevelHash()); assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("identity_expires")), 10); assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_expires")), 10); @@ -1549,13 +1589,13 @@ void tokenGenerateUsingCustomSiteKey(String apiVersion, Vertx vertx, VertxTestCo assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Email); + assertEquals(clientSiteId, advertisingTokenRequest.sourcePublisher.siteId); + assertArrayEquals(getRawUidFromIdentity(DiiType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingTokenRequest.rawUid.rawUid()); - RefreshToken refreshToken = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token")); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), refreshToken.userIdentity.id); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Email); + assertEquals(clientSiteId, tokenRefreshRequest.sourcePublisher.siteId); + assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), tokenRefreshRequest.firstLevelHash.firstLevelHash()); testContext.completeNow(); }); @@ -1583,16 +1623,18 @@ void tokenGenerateSaltsExpired(String apiVersion, Vertx vertx, VertxTestContext assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Email); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + assertTrue(advertisingTokenRequest.privacyBits.isLegacyBitSet()); + assertEquals(advertisingTokenRequest.privacyBits, PrivacyBits.DEFAULT); + assertFalse(advertisingTokenRequest.privacyBits.isClientSideTokenGenerated()); + assertFalse(advertisingTokenRequest.privacyBits.isClientSideTokenOptedOut()); + assertEquals(clientSiteId, advertisingTokenRequest.sourcePublisher.siteId); + assertArrayEquals(getRawUidFromIdentity(DiiType.Email, emailAddress, firstLevelSalt, rotatingSalt123.getSalt()), advertisingTokenRequest.rawUid.rawUid()); - RefreshToken refreshToken = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token")); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), refreshToken.userIdentity.id); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Email); + assertEquals(clientSiteId, tokenRefreshRequest.sourcePublisher.siteId); + assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(emailAddress, firstLevelSalt), tokenRefreshRequest.firstLevelHash.firstLevelHash()); assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("identity_expires")), 10); assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_expires")), 10); @@ -1899,7 +1941,7 @@ void tokenValidateWithEmailHash_Mismatch(String apiVersion, Vertx vertx, VertxTe void identityMapBothEmailAndHashSpecified(Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String emailAddress = "test@uid2.com"; - final String emailHash = TokenUtils.getIdentityHashString(emailAddress); + final String emailHash = TokenUtils.getDiiHashString(emailAddress); fakeAuth(clientSiteId, Role.MAPPER); setupSalts(); setupKeys(); @@ -1987,7 +2029,7 @@ void identityMapForSaltsExpired(Vertx vertx, VertxTestContext testContext) { @Test void identityMapForEmailHash(Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; - final String emailHash = TokenUtils.getIdentityHashString("test@uid2.com"); + final String emailHash = TokenUtils.getDiiHashString("test@uid2.com"); fakeAuth(clientSiteId, Role.MAPPER); setupSalts(); setupKeys(); @@ -2043,7 +2085,7 @@ void identityMapBatchBothEmailAndHashSpecified(String apiVersion, Vertx vertx, V req.put("email_hash", emailHashes); emails.add("test1@uid2.com"); - emailHashes.add(TokenUtils.getIdentityHashString("test2@uid2.com")); + emailHashes.add(TokenUtils.getDiiHashString("test2@uid2.com")); send(apiVersion, vertx, apiVersion + "/identity/map", false, null, req, 400, respJson -> { assertFalse(respJson.containsKey("body")); @@ -2182,8 +2224,8 @@ void identityMapBatchEmailHashes(String apiVersion, Vertx vertx, VertxTestContex JsonArray hashes = new JsonArray(); req.put("email_hash", hashes); final String[] email_hashes = { - TokenUtils.getIdentityHashString("test1@uid2.com"), - TokenUtils.getIdentityHashString("test2@uid2.com"), + TokenUtils.getDiiHashString("test1@uid2.com"), + TokenUtils.getDiiHashString("test2@uid2.com"), }; for (String email_hash : email_hashes) { @@ -2300,9 +2342,9 @@ void optOutStatusRequest(Map optedOutIds, int optedOutCount, Role assertEquals(optedOutCount, optOutJsonArray.size()); for (int i = 0; i < optOutJsonArray.size(); ++i) { JsonObject optOutObject = optOutJsonArray.getJsonObject(i); - String advertisingId = optOutObject.getString("advertising_id"); - assertTrue(optedOutIds.containsKey(advertisingId)); - long expectedTimestamp = Instant.ofEpochSecond(optedOutIds.get(advertisingId)).toEpochMilli(); + String rawUid = optOutObject.getString("advertising_id"); + assertTrue(optedOutIds.containsKey(rawUid)); + long expectedTimestamp = Instant.ofEpochSecond(optedOutIds.get(rawUid)).toEpochMilli(); assertEquals(expectedTimestamp, optOutObject.getLong("opted_out_since")); } testContext.completeNow(); @@ -2410,7 +2452,7 @@ void LogoutV2SaltsExpired(Vertx vertx, VertxTestContext testContext) { void tokenGenerateBothPhoneAndHashSpecified(String apiVersion, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String phone = "+15555555555"; - final String phoneHash = TokenUtils.getIdentityHashString(phone); + final String phoneHash = TokenUtils.getDiiHashString(phone); fakeAuth(clientSiteId, Role.GENERATOR); setupSalts(); setupKeys(); @@ -2456,9 +2498,9 @@ void tokenGenerateBothPhoneAndEmailSpecified(String apiVersion, Vertx vertx, Ver void tokenGenerateBothPhoneHashAndEmailHashSpecified(String apiVersion, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String phone = "+15555555555"; - final String phoneHash = TokenUtils.getIdentityHashString(phone); + final String phoneHash = TokenUtils.getDiiHashString(phone); final String emailAddress = "test@uid2.com"; - final String emailHash = TokenUtils.getIdentityHashString(emailAddress); + final String emailHash = TokenUtils.getDiiHashString(emailAddress); fakeAuth(clientSiteId, Role.GENERATOR); setupSalts(); setupKeys(); @@ -2495,31 +2537,37 @@ void tokenGenerateForPhone(String apiVersion, Vertx vertx, VertxTestContext test assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Phone); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Phone); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Phone); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); - - RefreshToken refreshToken = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), IdentityType.Phone); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt), refreshToken.userIdentity.id); - - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_from")), 10); + assertAdvertisingTokenRefreshTokenRequests(advertisingTokenRequest, tokenRefreshRequest, clientSiteId, + getRawUidFromIdentity(DiiType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()), + PrivacyBits.DEFAULT, + body, + TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt)); testContext.completeNow(); }); } + void verifyFirstLevelHashIdentityAndEstablishedAt(byte[] expectedFirstLevelHash, + TokenRefreshRequest tokenRefreshRequest, + JsonObject receivedJsonBody, + Instant expectedEstablishedTime) { + + assertArrayEquals(expectedFirstLevelHash, tokenRefreshRequest.firstLevelHash.firstLevelHash()); + assertEquals(expectedEstablishedTime, tokenRefreshRequest.firstLevelHash.establishedAt()); + assertTrue(tokenRefreshRequest.firstLevelHash.establishedAt().toEpochMilli() < receivedJsonBody.getLong("identity_expires") ); + assertTrue(tokenRefreshRequest.firstLevelHash.establishedAt().toEpochMilli() < receivedJsonBody.getLong("refresh_expires") ); + assertTrue(tokenRefreshRequest.firstLevelHash.establishedAt().toEpochMilli() < receivedJsonBody.getLong("refresh_from") ); + } + @ParameterizedTest @ValueSource(strings = {"v1", "v2"}) void tokenGenerateForPhoneHash(String apiVersion, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String phone = "+15555555555"; - final String phoneHash = TokenUtils.getIdentityHashString(phone); + final String phoneHash = TokenUtils.getDiiHashString(phone); fakeAuth(clientSiteId, Role.GENERATOR); setupSalts(); setupKeys(); @@ -2534,20 +2582,15 @@ void tokenGenerateForPhoneHash(String apiVersion, Vertx vertx, VertxTestContext assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Phone); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Phone); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Phone); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + assertAdvertisingTokenRefreshTokenRequests(advertisingTokenRequest, tokenRefreshRequest, clientSiteId, + getRawUidFromIdentity(DiiType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()), + PrivacyBits.DEFAULT, + body, + TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt)); - RefreshToken refreshToken = decodeRefreshToken(encoder, body.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), IdentityType.Phone); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt), refreshToken.userIdentity.id); - - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(body.getLong("refresh_from")), 10); testContext.completeNow(); }); @@ -2567,33 +2610,73 @@ void tokenGenerateThenRefreshForPhone(String apiVersion, Vertx vertx, VertxTestC JsonObject bodyJson = genRespJson.getJsonObject("body"); assertNotNull(bodyJson); + EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); + AdvertisingTokenRequest firstAdvertisingTokenRequest = validateAndGetToken(encoder, bodyJson, + DiiType.Phone); String genRefreshToken = bodyJson.getString("refresh_token"); + TokenRefreshRequest firstTokenRefreshRequest = decodeRefreshToken(encoder, bodyJson.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"), DiiType.Phone); when(this.optOutStore.getLatestEntry(any())).thenReturn(null); + byte[] expectedRawUidIdentity = getRawUidFromIdentity(DiiType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()); + byte[] expectedFirstLevelHashIdentity = TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt); + + assertAdvertisingTokenRefreshTokenRequests(firstAdvertisingTokenRequest, firstTokenRefreshRequest, clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + sendTokenRefresh(apiVersion, vertx, testContext, genRefreshToken, bodyJson.getString("refresh_response_key"), 200, refreshRespJson -> { assertEquals("success", refreshRespJson.getString("status")); JsonObject refreshBody = refreshRespJson.getJsonObject("body"); assertNotNull(refreshBody); - EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - - AdvertisingToken advertisingToken = validateAndGetToken(encoder, refreshBody, IdentityType.Phone); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenGenerated()); - assertFalse(PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits).isClientSideTokenOptedOut()); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); - assertArrayEquals(getAdvertisingIdFromIdentity(IdentityType.Phone, phone, firstLevelSalt, rotatingSalt123.getSalt()), advertisingToken.userIdentity.id); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, refreshBody, DiiType.Phone); String refreshTokenStringNew = refreshBody.getString(apiVersion.equals("v2") ? "decrypted_refresh_token" : "refresh_token"); assertNotEquals(genRefreshToken, refreshTokenStringNew); - RefreshToken refreshToken = decodeRefreshToken(encoder, refreshTokenStringNew, IdentityType.Phone); - assertEquals(clientSiteId, refreshToken.publisherIdentity.siteId); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(phone, firstLevelSalt), refreshToken.userIdentity.id); + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, refreshTokenStringNew, DiiType.Phone); - assertEqualsClose(now.plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("identity_expires")), 10); - assertEqualsClose(now.plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_expires")), 10); - assertEqualsClose(now.plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_from")), 10); + // assert if the ad/refresh tokens from original token/generate is same as the ad/refresh tokens from token/refresh + assertAdvertisingTokenRefreshTokenRequests( + advertisingTokenRequest, + firstTokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + assertAdvertisingTokenRefreshTokenRequests( + firstAdvertisingTokenRequest, + tokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + assertAdvertisingTokenRefreshTokenRequests( + advertisingTokenRequest, + tokenRefreshRequest, + clientSiteId, + expectedRawUidIdentity, + PrivacyBits.DEFAULT, + bodyJson, + expectedFirstLevelHashIdentity); + + assertTokenStatusMetrics( + clientSiteId, + apiVersion.equals("v1") ? TokenResponseStatsCollector.Endpoint.GenerateV1 : TokenResponseStatsCollector.Endpoint.GenerateV2, + TokenResponseStatsCollector.ResponseStatus.Success, + //didn't set any specific header + TokenResponseStatsCollector.PlatformType.Other); + assertTokenStatusMetrics( + clientSiteId, + apiVersion.equals("v1") ? TokenResponseStatsCollector.Endpoint.RefreshV1 : TokenResponseStatsCollector.Endpoint.RefreshV2, + TokenResponseStatsCollector.ResponseStatus.Success, + //didn't set any specific header + TokenResponseStatsCollector.PlatformType.Other); testContext.completeNow(); }); @@ -2775,7 +2858,7 @@ void tokenRefreshOptOutBeforeLoginForPhone(String apiVersion, Vertx vertx, Vertx void identityMapBothPhoneAndHashSpecified(Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String phone = "+15555555555"; - final String phoneHash = TokenUtils.getIdentityHashString(phone); + final String phoneHash = TokenUtils.getDiiHashString(phone); fakeAuth(clientSiteId, Role.MAPPER); setupSalts(); setupKeys(); @@ -2819,7 +2902,7 @@ void identityMapForPhone(Vertx vertx, VertxTestContext testContext) { void identityMapForPhoneHash(Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 201; final String phone = "+15555555555"; - final String phonneHash = TokenUtils.getIdentityHashString(phone); + final String phonneHash = TokenUtils.getDiiHashString(phone); fakeAuth(clientSiteId, Role.MAPPER); setupSalts(); setupKeys(); @@ -2894,7 +2977,7 @@ void identityMapBatchBothPhoneAndHashSpecified(String apiVersion, Vertx vertx, V req.put("phone_hash", phoneHashes); phones.add("+15555555555"); - phoneHashes.add(TokenUtils.getIdentityHashString("+15555555555")); + phoneHashes.add(TokenUtils.getDiiHashString("+15555555555")); send(apiVersion, vertx, apiVersion + "/identity/map", false, null, req, 400, respJson -> { assertFalse(respJson.containsKey("body")); @@ -2936,8 +3019,8 @@ void identityMapBatchPhoneHashes(String apiVersion, Vertx vertx, VertxTestContex JsonArray hashes = new JsonArray(); req.put("phone_hash", hashes); final String[] email_hashes = { - TokenUtils.getIdentityHashString("+15555555555"), - TokenUtils.getIdentityHashString("+15555555556"), + TokenUtils.getDiiHashString("+15555555555"), + TokenUtils.getDiiHashString("+15555555556"), }; for (String email_hash : email_hashes) { @@ -3018,7 +3101,7 @@ void tokenGenerateRespectOptOutOption(String policyParameterKey, Vertx vertx, Ve setupKeys(); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(now.minus(1, ChronoUnit.HOURS)); JsonObject req = new JsonObject(); @@ -3049,7 +3132,7 @@ void identityMapDefaultOption(String apiVersion, Vertx vertx, VertxTestContext t setupKeys(); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(now.minus(1, ChronoUnit.HOURS)); JsonObject req = new JsonObject(); @@ -3090,7 +3173,7 @@ void identityMapRespectOptOutOption(String apiVersion, String policyParameterKey setupKeys(); // the clock value shouldn't matter here - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(now.minus(1, ChronoUnit.HOURS)); JsonObject req = new JsonObject(); @@ -3226,7 +3309,7 @@ void cstgNoIdentityHashProvided(Vertx vertx, VertxTestContext testContext) throw }) void cstgDomainNameCheckFails(String httpOrigin, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend(); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", httpOrigin, @@ -3255,7 +3338,7 @@ void cstgDomainNameCheckFails(String httpOrigin, Vertx vertx, VertxTestContext t }) void cstgAppNameCheckFails(String appName, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend(Collections.emptyList(), List.of("com.123.Game.App.android")); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); sendCstg(vertx, "v2/token/client-generate", null, @@ -3290,7 +3373,7 @@ void cstgDomainNameCheckFailsAndLogInvalidHttpOrigin(String httpOrigin, Vertx ve this.uidOperatorVerticle.setLastInvalidOriginProcessTime(Instant.now().minusSeconds(3600)); setupCstgBackend(); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", httpOrigin, @@ -3321,7 +3404,7 @@ void cstgLogsInvalidAppName(String appName, Vertx vertx, VertxTestContext testCo this.uidOperatorVerticle.setLastInvalidOriginProcessTime(Instant.now().minusSeconds(3600)); setupCstgBackend(); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); sendCstg(vertx, "v2/token/client-generate", null, @@ -3367,7 +3450,7 @@ void cstgDisabledAsUnauthorized(Vertx vertx, VertxTestContext testContext) throw requestJson.put("timestamp", timestamp); requestJson.put("subscription_id", subscriptionID); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), null); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), null); sendCstg(vertx, "v2/token/client-generate", null, @@ -3405,7 +3488,7 @@ void cstgDomainNameCheckFailsAndLogSeveralInvalidHttpOrigin(String httpOrigin, V setupCstgBackend(); when(siteProvider.getSite(124)).thenReturn(new Site(124, "test2", true, new HashSet<>())); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", httpOrigin, @@ -3435,7 +3518,7 @@ void cstgDomainNameCheckFailsAndLogSeveralInvalidHttpOrigin(String httpOrigin, V }) void cstgDomainNameCheckPasses(String httpOrigin, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend("cstg.co.uk", "cstg2.com", "localhost"); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", httpOrigin, @@ -3449,7 +3532,7 @@ void cstgDomainNameCheckPasses(String httpOrigin, Vertx vertx, VertxTestContext JsonObject refreshBody = respJson.getJsonObject("body"); assertNotNull(refreshBody); var encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - validateAndGetToken(encoder, refreshBody, IdentityType.Email); //to validate token version is correct + validateAndGetToken(encoder, refreshBody, DiiType.Email); //to validate token version is correct testContext.completeNow(); }); } @@ -3462,7 +3545,7 @@ void cstgDomainNameCheckPasses(String httpOrigin, Vertx vertx, VertxTestContext }) void cstgAppNameCheckPasses(String appName, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend(Collections.emptyList(), List.of("com.123.Game.App.android", "123456789")); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli(), appName); sendCstg(vertx, "v2/token/client-generate", null, @@ -3476,7 +3559,7 @@ void cstgAppNameCheckPasses(String appName, Vertx vertx, VertxTestContext testCo JsonObject refreshBody = respJson.getJsonObject("body"); assertNotNull(refreshBody); var encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - validateAndGetToken(encoder, refreshBody, IdentityType.Email); //to validate token version is correct + validateAndGetToken(encoder, refreshBody, DiiType.Email); //to validate token version is correct assertTokenStatusMetrics( clientSideTokenGenerateSiteId, TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2, @@ -3943,18 +4026,18 @@ private Tuple.Tuple2 createClientSideTokenGenerateRequest return new Tuple.Tuple2<>(requestJson, secretKey); } - private Tuple.Tuple2 createClientSideTokenGenerateRequest(IdentityType identityType, String rawId, long timestamp) throws NoSuchAlgorithmException, InvalidKeyException { - return createClientSideTokenGenerateRequest(identityType, rawId, timestamp, null); + private Tuple.Tuple2 createClientSideTokenGenerateRequest(DiiType diiType, String rawId, long timestamp) throws NoSuchAlgorithmException, InvalidKeyException { + return createClientSideTokenGenerateRequest(diiType, rawId, timestamp, null); } - private Tuple.Tuple2 createClientSideTokenGenerateRequest(IdentityType identityType, String rawId, long timestamp, String appName) throws NoSuchAlgorithmException, InvalidKeyException { + private Tuple.Tuple2 createClientSideTokenGenerateRequest(DiiType diiType, String rawId, long timestamp, String appName) throws NoSuchAlgorithmException, InvalidKeyException { JsonObject identity = new JsonObject(); - if(identityType == IdentityType.Email) { + if(diiType == DiiType.Email) { identity.put("email_hash", getSha256(rawId)); } - else if(identityType == IdentityType.Phone) { + else if(diiType == DiiType.Phone) { identity.put("phone_hash", getSha256(rawId)); } else { //can't be other types @@ -3975,17 +4058,17 @@ private Tuple.Tuple2 createClientSideTokenGenerateRequest "test@example.com,Email", "+61400000000,Phone" }) - void cstgUserOptsOutAfterTokenGenerate(String id, IdentityType identityType, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { + void cstgUserOptsOutAfterTokenGenerate(String id, DiiType diiType, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend("cstg.co.uk"); - final Tuple.Tuple2 data = createClientSideTokenGenerateRequest(identityType, id, Instant.now().toEpochMilli()); + final Tuple.Tuple2 data = createClientSideTokenGenerateRequest(diiType, id, Instant.now().toEpochMilli()); // When we generate the token the user hasn't opted out. - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(null); final EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(UserIdentity.class); + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(FirstLevelHash.class); sendCstg(vertx, "v2/token/client-generate", @@ -3996,19 +4079,20 @@ void cstgUserOptsOutAfterTokenGenerate(String id, IdentityType identityType, Ver testContext, response -> { verify(optOutStore, times(1)).getLatestEntry(argumentCaptor.capture()); - assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(id, firstLevelSalt), argumentCaptor.getValue().id); + assertArrayEquals(TokenUtils.getFirstLevelHashFromIdentity(id, firstLevelSalt), + argumentCaptor.getValue().firstLevelHash()); assertEquals("success", response.getString("status")); final JsonObject genBody = response.getJsonObject("body"); - final AdvertisingToken advertisingToken = validateAndGetToken(encoder, genBody, identityType); - final RefreshToken refreshToken = decodeRefreshToken(encoder, decodeV2RefreshToken(response), identityType); + final AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, genBody, diiType); + final TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, decodeV2RefreshToken(response), diiType); - assertAreClientSideGeneratedTokens(advertisingToken, refreshToken, clientSideTokenGenerateSiteId, identityType, id); + assertAreClientSideGeneratedTokens(advertisingTokenRequest, tokenRefreshRequest, clientSideTokenGenerateSiteId, diiType, id); // When we refresh the token the user has opted out. - when(optOutStore.getLatestEntry(any(UserIdentity.class))) - .thenReturn(advertisingToken.userIdentity.establishedAt.plusSeconds(1)); + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) + .thenReturn(advertisingTokenRequest.establishedAt.plusSeconds(1)); sendTokenRefresh("v2", vertx, testContext, genBody.getString("refresh_token"), genBody.getString("refresh_response_key"), 200, refreshRespJson -> { assertEquals("optout", refreshRespJson.getString("status")); @@ -4028,19 +4112,19 @@ void cstgUserOptsOutAfterTokenGenerate(String id, IdentityType identityType, Ver "false,abc@abc.com,Email", "false,+61400000000,Phone", }) - void cstgSuccessForBothOptedAndNonOptedOutTest(boolean optOutExpected, String id, IdentityType identityType, + void cstgSuccessForBothOptedAndNonOptedOutTest(boolean optOutExpected, String id, DiiType diiType, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend("cstg.co.uk"); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(identityType, id, Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(diiType, id, Instant.now().toEpochMilli()); if(optOutExpected) { - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(Instant.now().minus(1, ChronoUnit.HOURS)); } else { //not expectedOptedOut - when(optOutStore.getLatestEntry(any(UserIdentity.class))) + when(optOutStore.getLatestEntry(any(FirstLevelHash.class))) .thenReturn(null); } @@ -4065,11 +4149,26 @@ void cstgSuccessForBothOptedAndNonOptedOutTest(boolean optOutExpected, String id decodeV2RefreshToken(respJson); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, genBody, identityType); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, genBody, diiType); + + TokenRefreshRequest tokenRefreshRequest = decodeRefreshToken(encoder, genBody.getString("decrypted_refresh_token"), diiType); + - RefreshToken refreshToken = decodeRefreshToken(encoder, genBody.getString("decrypted_refresh_token"), identityType); - assertAreClientSideGeneratedTokens(advertisingToken, refreshToken, clientSideTokenGenerateSiteId, identityType, id); + byte[] expectedRawUidIdentity = getRawUidFromIdentity(diiType, id, firstLevelSalt, rotatingSalt123.getSalt()); + byte[] expectedFirstLevelHashIdentity = TokenUtils.getFirstLevelHashFromIdentity(id, firstLevelSalt); + + PrivacyBits expectedPrivacyBits = new PrivacyBits(); + expectedPrivacyBits.setLegacyBit(); + expectedPrivacyBits.setClientSideTokenGenerate(); + + assertAdvertisingTokenRefreshTokenRequests(advertisingTokenRequest, tokenRefreshRequest, + clientSideTokenGenerateSiteId, + expectedRawUidIdentity, + expectedPrivacyBits, + genBody, + expectedFirstLevelHashIdentity); + assertAreClientSideGeneratedTokens(advertisingTokenRequest, tokenRefreshRequest, clientSideTokenGenerateSiteId, diiType, id); assertEqualsClose(Instant.now().plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(genBody.getLong("identity_expires")), 10); assertEqualsClose(Instant.now().plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(genBody.getLong("refresh_expires")), 10); assertEqualsClose(Instant.now().plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(genBody.getLong("refresh_from")), 10); @@ -4090,13 +4189,19 @@ void cstgSuccessForBothOptedAndNonOptedOutTest(boolean optOutExpected, String id EncryptedTokenEncoder encoder2 = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); //make sure the new advertising token from refresh looks right - AdvertisingToken adTokenFromRefresh = validateAndGetToken(encoder2, refreshBody, identityType); + AdvertisingTokenRequest adTokenFromRefresh = validateAndGetToken(encoder2, refreshBody, diiType); String refreshTokenStringNew = refreshBody.getString("decrypted_refresh_token"); assertNotEquals(genRefreshToken, refreshTokenStringNew); - RefreshToken refreshTokenAfterRefresh = decodeRefreshToken(encoder, refreshTokenStringNew, identityType); + TokenRefreshRequest refreshTokenAfterRefreshSource = decodeRefreshToken(encoder, refreshTokenStringNew, diiType); - assertAreClientSideGeneratedTokens(adTokenFromRefresh, refreshTokenAfterRefresh, clientSideTokenGenerateSiteId, identityType, id); + assertAdvertisingTokenRefreshTokenRequests(adTokenFromRefresh, refreshTokenAfterRefreshSource, + clientSideTokenGenerateSiteId, + expectedRawUidIdentity, + expectedPrivacyBits, + genBody, + expectedFirstLevelHashIdentity); + assertAreClientSideGeneratedTokens(adTokenFromRefresh, refreshTokenAfterRefreshSource, clientSideTokenGenerateSiteId, diiType, id); assertEqualsClose(Instant.now().plusMillis(identityExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("identity_expires")), 10); assertEqualsClose(Instant.now().plusMillis(refreshExpiresAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_expires")), 10); assertEqualsClose(Instant.now().plusMillis(refreshIdentityAfter.toMillis()), Instant.ofEpochMilli(refreshBody.getLong("refresh_from")), 10); @@ -4121,7 +4226,7 @@ void cstgSuccessForBothOptedAndNonOptedOutTest(boolean optOutExpected, String id void cstgSaltsExpired(String httpOrigin, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { when(saltProviderSnapshot.getExpires()).thenReturn(Instant.now().minus(1, ChronoUnit.HOURS)); setupCstgBackend("cstg.co.uk", "cstg2.com", "localhost"); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", httpOrigin, @@ -4135,7 +4240,7 @@ void cstgSaltsExpired(String httpOrigin, Vertx vertx, VertxTestContext testConte JsonObject refreshBody = respJson.getJsonObject("body"); assertNotNull(refreshBody); var encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - validateAndGetToken(encoder, refreshBody, IdentityType.Email); //to validate token version is correct + validateAndGetToken(encoder, refreshBody, DiiType.Email); //to validate token version is correct verify(shutdownHandler, atLeastOnce()).handleSaltRetrievalResponse(true); @@ -4147,7 +4252,7 @@ void cstgSaltsExpired(String httpOrigin, Vertx vertx, VertxTestContext testConte void cstgNoActiveKey(Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException { setupCstgBackend("cstg.co.uk"); setupKeys(true); - Tuple.Tuple2 data = createClientSideTokenGenerateRequest(IdentityType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); + Tuple.Tuple2 data = createClientSideTokenGenerateRequest(DiiType.Email, "random@unifiedid.com", Instant.now().toEpochMilli()); sendCstg(vertx, "v2/token/client-generate", "http://cstg.co.uk", @@ -4190,20 +4295,20 @@ void cstgInvalidInput(String identityType, String rawUID, Vertx vertx, VertxTest }); } - private void assertAreClientSideGeneratedTokens(AdvertisingToken advertisingToken, RefreshToken refreshToken, int siteId, IdentityType identityType, String identity) { - assertAreClientSideGeneratedTokens(advertisingToken, - refreshToken, + private void assertAreClientSideGeneratedTokens(AdvertisingTokenRequest advertisingTokenRequest, TokenRefreshRequest tokenRefreshRequest, int siteId, DiiType diiType, String identity) { + assertAreClientSideGeneratedTokens(advertisingTokenRequest, + tokenRefreshRequest, siteId, - identityType, + diiType, identity, false); } - private void assertAreClientSideGeneratedTokens(AdvertisingToken advertisingToken, RefreshToken refreshToken, int siteId, IdentityType identityType, String identity, boolean expectedOptOut) { - final PrivacyBits advertisingTokenPrivacyBits = PrivacyBits.fromInt(advertisingToken.userIdentity.privacyBits); - final PrivacyBits refreshTokenPrivacyBits = PrivacyBits.fromInt(refreshToken.userIdentity.privacyBits); + private void assertAreClientSideGeneratedTokens(AdvertisingTokenRequest advertisingTokenRequest, TokenRefreshRequest tokenRefreshRequest, int siteId, DiiType diiType, String identity, boolean expectedOptOut) { + final PrivacyBits advertisingTokenPrivacyBits = advertisingTokenRequest.privacyBits; + final PrivacyBits refreshTokenPrivacyBits = tokenRefreshRequest.privacyBits; - final byte[] advertisingId = getAdvertisingIdFromIdentity(identityType, + final byte[] rawUid = getRawUidFromIdentity(diiType, identity, firstLevelSalt, rotatingSalt123.getSalt()); @@ -4217,11 +4322,11 @@ private void assertAreClientSideGeneratedTokens(AdvertisingToken advertisingToke () -> assertTrue(refreshTokenPrivacyBits.isClientSideTokenGenerated(), "Refresh token privacy bits CSTG flag is incorrect"), () -> assertEquals(expectedOptOut, refreshTokenPrivacyBits.isClientSideTokenOptedOut(), "Refresh token privacy bits CSTG optout flag is incorrect"), - () -> assertEquals(siteId, advertisingToken.publisherIdentity.siteId, "Advertising token site ID is incorrect"), - () -> assertEquals(siteId, refreshToken.publisherIdentity.siteId, "Refresh token site ID is incorrect"), + () -> assertEquals(siteId, advertisingTokenRequest.sourcePublisher.siteId, "Advertising token site ID is incorrect"), + () -> assertEquals(siteId, tokenRefreshRequest.sourcePublisher.siteId, "Refresh token site ID is incorrect"), - () -> assertArrayEquals(advertisingId, advertisingToken.userIdentity.id, "Advertising token ID is incorrect"), - () -> assertArrayEquals(firstLevelHash, refreshToken.userIdentity.id, "Refresh token ID is incorrect") + () -> assertArrayEquals(rawUid, advertisingTokenRequest.rawUid.rawUid(), "Advertising token ID is incorrect"), + () -> assertArrayEquals(firstLevelHash, tokenRefreshRequest.firstLevelHash.firstLevelHash(), "Refresh token ID is incorrect") ); } @@ -4395,7 +4500,7 @@ void getActiveKeyTest() { @ValueSource(strings = {"MultiKeysets", "AddKey", "RotateKey", "DisableActiveKey", "DisableDefaultKeyset"}) void tokenGenerateRotatingKeysets_GENERATOR(String testRun, Vertx vertx, VertxTestContext testContext) { final int clientSiteId = 101; - final String emailHash = TokenUtils.getIdentityHashString("test@uid2.com"); + final String emailHash = TokenUtils.getDiiHashString("test@uid2.com"); fakeAuth(clientSiteId, Role.GENERATOR); MultipleKeysetsTests test = new MultipleKeysetsTests(); //To read these tests, open the MultipleKeysetsTests() constructor in another window so you can see the keyset contents and validate expectations @@ -4451,16 +4556,16 @@ void tokenGenerateRotatingKeysets_GENERATOR(String testRun, Vertx vertx, VertxTe assertNotNull(body); EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(new KeyManager(keysetKeyStore, keysetProvider)); - AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email); - assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId); + AdvertisingTokenRequest advertisingTokenRequest = validateAndGetToken(encoder, body, DiiType.Email); + assertEquals(clientSiteId, advertisingTokenRequest.sourcePublisher.siteId); //Uses a key from default keyset int clientKeyId; - if (advertisingToken.version == TokenVersion.V3 || advertisingToken.version == TokenVersion.V4) { + if (advertisingTokenRequest.version == TokenVersion.V3 || advertisingTokenRequest.version == TokenVersion.V4) { String advertisingTokenString = body.getString("advertising_token"); byte[] bytes = null; - if (advertisingToken.version == TokenVersion.V3) { + if (advertisingTokenRequest.version == TokenVersion.V3) { bytes = EncodingUtils.fromBase64(advertisingTokenString); - } else if (advertisingToken.version == TokenVersion.V4) { + } else if (advertisingTokenRequest.version == TokenVersion.V4) { bytes = Uid2Base64UrlCoder.decode(advertisingTokenString); //same as V3 but use Base64URL encoding } final Buffer b = Buffer.buffer(bytes); @@ -4470,7 +4575,7 @@ void tokenGenerateRotatingKeysets_GENERATOR(String testRun, Vertx vertx, VertxTe final Buffer masterPayload = Buffer.buffer(masterPayloadBytes); clientKeyId = masterPayload.getInt(29); } else { - clientKeyId = advertisingToken.publisherIdentity.clientKeyId; + clientKeyId = advertisingTokenRequest.sourcePublisher.clientKeyId; } switch (testRun) { case "MultiKeysets": diff --git a/src/test/java/com/uid2/operator/V2RequestUtilTest.java b/src/test/java/com/uid2/operator/V2RequestUtilTest.java index f296411e0..ee42d6a21 100644 --- a/src/test/java/com/uid2/operator/V2RequestUtilTest.java +++ b/src/test/java/com/uid2/operator/V2RequestUtilTest.java @@ -3,7 +3,7 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; -import com.uid2.operator.model.IdentityScope; +import com.uid2.operator.model.identities.IdentityScope; import com.uid2.operator.model.KeyManager; import com.uid2.operator.service.V2RequestUtil; import com.uid2.shared.IClock; diff --git a/src/test/java/com/uid2/operator/benchmark/BenchmarkCommon.java b/src/test/java/com/uid2/operator/benchmark/BenchmarkCommon.java index 4cc327e9f..b11c97f09 100644 --- a/src/test/java/com/uid2/operator/benchmark/BenchmarkCommon.java +++ b/src/test/java/com/uid2/operator/benchmark/BenchmarkCommon.java @@ -3,6 +3,10 @@ import com.uid2.operator.Const; import com.uid2.operator.Main; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.DiiType; +import com.uid2.operator.model.identities.FirstLevelHash; +import com.uid2.operator.model.identities.HashedDii; +import com.uid2.operator.model.identities.IdentityScope; import com.uid2.operator.service.EncryptedTokenEncoder; import com.uid2.operator.service.IUIDOperatorService; import com.uid2.operator.service.UIDOperatorService; @@ -148,18 +152,17 @@ static ICloudStorage make1mOptOutEntryStorage(String salt, List out_gene return storage; } - static UserIdentity[] createUserIdentities() { - UserIdentity[] arr = new UserIdentity[65536]; + static HashedDii[] createHashedDiiIdentities() { + HashedDii[] arr = new HashedDii[65536]; for (int i = 0; i < 65536; i++) { - final byte[] id = new byte[33]; - new Random().nextBytes(id); - arr[i] = new UserIdentity(IdentityScope.UID2, IdentityType.Email, id, 0, - Instant.now().minusSeconds(120), Instant.now().minusSeconds(60)); + final byte[] diiHash = new byte[33]; + new Random().nextBytes(diiHash); + arr[i] = new HashedDii(IdentityScope.UID2, DiiType.Email, diiHash); } return arr; } - static PublisherIdentity createPublisherIdentity() throws Exception { + static SourcePublisher createSourcePublisher() throws Exception { RotatingClientKeyProvider clients = new RotatingClientKeyProvider( new EmbeddedResourceStorage(Main.class), new GlobalScope(new CloudPath("/com.uid2.core/test/clients/metadata.json"))); @@ -167,7 +170,7 @@ static PublisherIdentity createPublisherIdentity() throws Exception { for (ClientKey client : clients.getAll()) { if (client.hasRole(Role.GENERATOR)) { - return new PublisherIdentity(client.getSiteId(), 0, 0); + return new SourcePublisher(client.getSiteId()); } } throw new IllegalStateException("embedded resource does not include any publisher key"); @@ -187,14 +190,14 @@ public StaticOptOutStore(ICloudStorage storage, JsonObject jsonConfig, Collectio } @Override - public Instant getLatestEntry(UserIdentity firstLevelHashIdentity) { - long epochSecond = this.snapshot.getOptOutTimestamp(firstLevelHashIdentity.id); + public Instant getLatestEntry(FirstLevelHash firstLevelHash) { + long epochSecond = this.snapshot.getOptOutTimestamp(firstLevelHash.firstLevelHash()); Instant instant = epochSecond > 0 ? Instant.ofEpochSecond(epochSecond) : null; return instant; } @Override - public void addEntry(UserIdentity firstLevelHashIdentity, byte[] advertisingId, Handler> handler) { + public void addEntry(FirstLevelHash firstLevelHash, byte[] advertisingId, Handler> handler) { // noop } diff --git a/src/test/java/com/uid2/operator/benchmark/IdentityMapBenchmark.java b/src/test/java/com/uid2/operator/benchmark/IdentityMapBenchmark.java index 6f9d19900..7151d8c7e 100644 --- a/src/test/java/com/uid2/operator/benchmark/IdentityMapBenchmark.java +++ b/src/test/java/com/uid2/operator/benchmark/IdentityMapBenchmark.java @@ -1,6 +1,7 @@ package com.uid2.operator.benchmark; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.HashedDii; import com.uid2.operator.service.IUIDOperatorService; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -10,13 +11,13 @@ public class IdentityMapBenchmark { private static final IUIDOperatorService uidService; - private static final UserIdentity[] userIdentities; + private static final HashedDii[] hashedDiiIdentities; private static int idx = 0; static { try { uidService = BenchmarkCommon.createUidOperatorService(); - userIdentities = BenchmarkCommon.createUserIdentities(); + hashedDiiIdentities = BenchmarkCommon.createHashedDiiIdentities(); } catch (Exception e) { throw new RuntimeException(e); } @@ -24,13 +25,13 @@ public class IdentityMapBenchmark { @Benchmark @BenchmarkMode(Mode.Throughput) - public MappedIdentity IdentityMapRawThroughput() { - return uidService.map(userIdentities[(idx++) & 65535], Instant.now()); + public IdentityMapResponseItem IdentityMapRawThroughput() { + return uidService.map(hashedDiiIdentities[(idx++) & 65535], Instant.now()); } @Benchmark @BenchmarkMode(Mode.Throughput) - public MappedIdentity IdentityMapWithOptOutThroughput() { - return uidService.mapIdentity(new MapRequest(userIdentities[(idx++) & 65535], OptoutCheckPolicy.RespectOptOut, Instant.now())); + public IdentityMapResponseItem IdentityMapWithOptOutThroughput() { + return uidService.mapHashedDii(new IdentityMapRequestItem(hashedDiiIdentities[(idx++) & 65535], OptoutCheckPolicy.RespectOptOut, Instant.now())); } } diff --git a/src/test/java/com/uid2/operator/benchmark/TokenEndecBenchmark.java b/src/test/java/com/uid2/operator/benchmark/TokenEndecBenchmark.java index aaa821db9..6b7e2c01c 100644 --- a/src/test/java/com/uid2/operator/benchmark/TokenEndecBenchmark.java +++ b/src/test/java/com/uid2/operator/benchmark/TokenEndecBenchmark.java @@ -1,6 +1,7 @@ package com.uid2.operator.benchmark; import com.uid2.operator.model.*; +import com.uid2.operator.model.identities.HashedDii; import com.uid2.operator.service.EncryptedTokenEncoder; import com.uid2.operator.service.IUIDOperatorService; import org.openjdk.jmh.annotations.Benchmark; @@ -13,20 +14,20 @@ public class TokenEndecBenchmark { private static final IUIDOperatorService uidService; - private static final UserIdentity[] userIdentities; - private static final PublisherIdentity publisher; + private static final HashedDii[] hashedDiiIdentities; + private static final SourcePublisher publisher; private static final EncryptedTokenEncoder encoder; - private static final IdentityTokens[] generatedTokens; + private static final TokenGenerateResponse[] generatedTokens; private static int idx = 0; static { try { uidService = BenchmarkCommon.createUidOperatorService(); - userIdentities = BenchmarkCommon.createUserIdentities(); - publisher = BenchmarkCommon.createPublisherIdentity(); + hashedDiiIdentities = BenchmarkCommon.createHashedDiiIdentities(); + publisher = BenchmarkCommon.createSourcePublisher(); encoder = BenchmarkCommon.createTokenEncoder(); generatedTokens = createAdvertisingTokens(); - if (generatedTokens.length < 65536 || userIdentities.length < 65536) { + if (generatedTokens.length < 65536 || hashedDiiIdentities.length < 65536) { throw new IllegalStateException("must create more than 65535 test candidates."); } } catch (Exception e) { @@ -34,30 +35,30 @@ public class TokenEndecBenchmark { } } - static IdentityTokens[] createAdvertisingTokens() { - List tokens = new ArrayList<>(); - for (int i = 0; i < userIdentities.length; i++) { + static TokenGenerateResponse[] createAdvertisingTokens() { + List tokens = new ArrayList<>(); + for (int i = 0; i < hashedDiiIdentities.length; i++) { tokens.add( - uidService.generateIdentity(new IdentityRequest( + uidService.generateIdentity(new TokenGenerateRequest( publisher, - userIdentities[i], + hashedDiiIdentities[i], OptoutCheckPolicy.DoNotRespect))); } - return tokens.toArray(new IdentityTokens[tokens.size()]); + return tokens.toArray(new TokenGenerateResponse[tokens.size()]); } @Benchmark @BenchmarkMode(Mode.Throughput) - public IdentityTokens TokenGenerationBenchmark() { - return uidService.generateIdentity(new IdentityRequest( + public TokenGenerateResponse TokenGenerationBenchmark() { + return uidService.generateIdentity(new TokenGenerateRequest( publisher, - userIdentities[(idx++) & 65535], + hashedDiiIdentities[(idx++) & 65535], OptoutCheckPolicy.DoNotRespect)); } @Benchmark @BenchmarkMode(Mode.Throughput) - public RefreshResponse TokenRefreshBenchmark() { + public TokenRefreshResponse TokenRefreshBenchmark() { return uidService.refreshIdentity( encoder.decodeRefreshToken( generatedTokens[(idx++) & 65535].getRefreshToken())); diff --git a/src/test/java/com/uid2/operator/utilTests/IdentityMapResponseItemTest.java b/src/test/java/com/uid2/operator/utilTests/IdentityMapResponseItemTest.java new file mode 100644 index 000000000..b45b2de6d --- /dev/null +++ b/src/test/java/com/uid2/operator/utilTests/IdentityMapResponseItemTest.java @@ -0,0 +1,30 @@ +package com.uid2.operator.utilTests; + +import com.uid2.operator.model.IdentityMapResponseItem; +import org.junit.Test; + +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import static org.junit.Assert.*; + + +public class IdentityMapResponseItemTest { + @Test + public void doRawUidResponseTest() throws NoSuchAlgorithmException { + assertEquals(IdentityMapResponseItem.OptoutIdentity.bucketId, ""); + assertTrue(IdentityMapResponseItem.OptoutIdentity.isOptedOut()); + + IdentityMapResponseItem optoutResponse = new IdentityMapResponseItem(new byte[33], null); + assertTrue(optoutResponse.isOptedOut()); + + byte[] rawUid = new byte[33]; + for(int i = 0; i < 33; i++) { + rawUid[i] = (byte) i; + } + + IdentityMapResponseItem generatedUid = new IdentityMapResponseItem(rawUid, "12345"); + assertFalse(generatedUid.isOptedOut()); + assertTrue(Arrays.equals(rawUid, generatedUid.rawUid)); + } +} diff --git a/src/test/java/com/uid2/operator/utilTests/PrivacyBitsTest.java b/src/test/java/com/uid2/operator/utilTests/PrivacyBitsTest.java new file mode 100644 index 000000000..91750bf87 --- /dev/null +++ b/src/test/java/com/uid2/operator/utilTests/PrivacyBitsTest.java @@ -0,0 +1,55 @@ +package com.uid2.operator.utilTests; + +import com.uid2.operator.util.PrivacyBits; +import org.junit.Test; +import java.security.NoSuchAlgorithmException; +import static org.junit.Assert.*; + + +public class PrivacyBitsTest { + @Test + public void doPrivacyBitsTest() throws NoSuchAlgorithmException { + assertEquals(PrivacyBits.DEFAULT.getAsInt(), 1); + PrivacyBits pb1 = new PrivacyBits(); + assertEquals(pb1.getAsInt(), 0); + assertEquals(pb1.hashCode(), 0); + assertNotEquals(pb1, PrivacyBits.fromInt(1)); + assertNotEquals(pb1, PrivacyBits.fromInt(121)); + assertFalse(pb1.isClientSideTokenGenerated()); + assertFalse(pb1.isClientSideTokenOptedOut()); + + pb1.setLegacyBit(); + assertEquals(pb1.getAsInt(), 0b1); + assertEquals(pb1.hashCode(), 0b1); + assertEquals(pb1, PrivacyBits.fromInt(1)); + assertNotEquals(pb1, PrivacyBits.fromInt(121)); + assertFalse(pb1.isClientSideTokenGenerated()); + assertFalse(pb1.isClientSideTokenOptedOut()); + + + pb1.setClientSideTokenGenerate(); + assertEquals(pb1.getAsInt(), 0b11); + assertEquals(pb1.hashCode(), 0b11); + assertEquals(pb1, PrivacyBits.fromInt(3)); + assertNotEquals(pb1, PrivacyBits.fromInt(121)); + assertTrue(pb1.isClientSideTokenGenerated()); + assertFalse(pb1.isClientSideTokenOptedOut()); + + + pb1.setClientSideTokenGenerateOptout(); + assertEquals(pb1.getAsInt(), 0b111); + assertEquals(pb1.hashCode(), 0b111); + assertEquals(pb1, PrivacyBits.fromInt(7)); + assertNotEquals(pb1, PrivacyBits.fromInt(121)); + assertTrue(pb1.isClientSideTokenGenerated()); + assertTrue(pb1.isClientSideTokenOptedOut()); + + PrivacyBits pb2 = new PrivacyBits(pb1); + assertEquals(pb2.getAsInt(), 0b111); + + PrivacyBits pb3 = PrivacyBits.fromInt(0b10110); + assertEquals(pb3.getAsInt(), 0b10110); + pb3.setLegacyBit(); + assertEquals(pb3.getAsInt(), 0b10111); + } +} diff --git a/src/test/java/com/uid2/operator/utilTests/TokenGenerateResponseTest.java b/src/test/java/com/uid2/operator/utilTests/TokenGenerateResponseTest.java new file mode 100644 index 000000000..e659c964b --- /dev/null +++ b/src/test/java/com/uid2/operator/utilTests/TokenGenerateResponseTest.java @@ -0,0 +1,50 @@ +package com.uid2.operator.utilTests; + +import com.uid2.operator.model.TokenGenerateResponse; +import com.uid2.shared.model.TokenVersion; +import io.vertx.core.json.JsonObject; +import org.junit.Test; + +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.junit.Assert.*; + + +public class TokenGenerateResponseTest { + @Test + public void doIdentityResponseTest() throws NoSuchAlgorithmException { + assertEquals(TokenGenerateResponse.OptOutResponse.getAdvertisingToken(), ""); + assertTrue(TokenGenerateResponse.OptOutResponse.isOptedOut()); + + TokenGenerateResponse nullAdTokenValue = new TokenGenerateResponse(null, TokenVersion.V4, "refreshToken", null,null,null); + assertTrue(nullAdTokenValue.isOptedOut()); + + Instant identityExpires = Instant.now(); + Instant refreshFrom = identityExpires.plus(5, ChronoUnit.MINUTES); + Instant refreshExpires = identityExpires.plus(10, ChronoUnit.MINUTES); + + + + TokenGenerateResponse response1 = new TokenGenerateResponse("adToken", TokenVersion.V3, "refreshToken", identityExpires + , refreshExpires, refreshFrom); + assertEquals(response1.getAdvertisingToken(), "adToken"); + assertEquals(response1.getAdvertisingTokenVersion(), TokenVersion.V3); + assertEquals(response1.getRefreshToken(), "refreshToken"); + assertEquals(response1.getIdentityExpires(), identityExpires); + assertEquals(response1.getRefreshExpires(), refreshExpires); + assertEquals(response1.getRefreshFrom(), refreshFrom); + + JsonObject jsonV1 = response1.toJsonV1(); + assertEquals(jsonV1.getString("advertising_token"), response1.getAdvertisingToken()); + assertEquals(jsonV1.getString("refresh_token"), response1.getRefreshToken()); + assertEquals(jsonV1.getLong("refresh_expires").longValue(), response1.getRefreshExpires().toEpochMilli()); + assertEquals(jsonV1.getLong("refresh_from").longValue(), response1.getRefreshFrom().toEpochMilli()); + + JsonObject jsonV0 = response1.toJsonV0(); + assertEquals(jsonV0.getString("advertisement_token"), response1.getAdvertisingToken()); + assertEquals(jsonV0.getString("advertising_token"), response1.getAdvertisingToken()); + assertEquals(jsonV0.getString("refresh_token"), response1.getRefreshToken()); + } +}