-
Notifications
You must be signed in to change notification settings - Fork 6
Identity Map V3 #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Identity Map V3 #83
Changes from all commits
9ab7827
7eade1a
ec27779
cf6168e
3df6e35
c1fb9d4
c3ef65d
18cfc01
ee0782a
7b3ae88
b8163ce
9a974ff
df2c22a
162e905
ec0b6fd
73ee0ab
80b92b9
b55415a
da5b65b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ dependencies/ | |
build/** | ||
.DS_Store | ||
*/node_modules/* | ||
.env |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.uid2.client; | ||
|
||
public class IdentityMapV3Client { | ||
/** | ||
* @param uid2BaseUrl The <a href="https://unifiedid.com/docs/getting-started/gs-environments">UID2 Base URL</a> | ||
* @param clientApiKey Your client API key | ||
* @param base64SecretKey Your client secret key | ||
*/ | ||
public IdentityMapV3Client(String uid2BaseUrl, String clientApiKey, String base64SecretKey) { | ||
identityMapHelper = new IdentityMapV3Helper(base64SecretKey); | ||
uid2ClientHelper = new Uid2ClientHelper(uid2BaseUrl, clientApiKey); | ||
} | ||
|
||
/** | ||
* @param identityMapInput represents the input required for <a href="https://unifiedid.com/docs/endpoints/post-identity-map">/identity/map</a> | ||
* @return an IdentityMapV3Response instance | ||
* @throws Uid2Exception if the response did not contain a "success" status, or the response code was not 200, or there was an error communicating with the provided UID2 Base URL | ||
*/ | ||
public IdentityMapV3Response generateIdentityMap(IdentityMapV3Input identityMapInput) { | ||
EnvelopeV2 envelope = identityMapHelper.createEnvelopeForIdentityMapRequest(identityMapInput); | ||
|
||
String responseString = uid2ClientHelper.makeRequest(envelope, "/v3/identity/map"); | ||
return identityMapHelper.createIdentityMapResponse(responseString, envelope, identityMapInput); | ||
} | ||
|
||
private final IdentityMapV3Helper identityMapHelper; | ||
private final Uid2ClientHelper uid2ClientHelper; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.uid2.client; | ||
|
||
import com.google.gson.Gson; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
|
||
public class IdentityMapV3Helper { | ||
/** | ||
* @param base64SecretKey your UID2 client secret | ||
*/ | ||
public IdentityMapV3Helper(String base64SecretKey) {uid2Helper = new Uid2Helper(base64SecretKey);} | ||
|
||
/** | ||
* @param identityMapInput represents the input required for <a href="https://unifiedid.com/docs/endpoints/post-identity-map">/identity/map</a> | ||
* @return an EnvelopeV2 instance to use in the POST body of <a href="https://unifiedid.com/docs/endpoints/post-identity-map">/identity/map</a> | ||
*/ | ||
public EnvelopeV2 createEnvelopeForIdentityMapRequest(IdentityMapV3Input identityMapInput) { | ||
byte[] jsonBytes = new Gson().toJson(identityMapInput).getBytes(StandardCharsets.UTF_8); | ||
return uid2Helper.createEnvelopeV2(jsonBytes); | ||
} | ||
|
||
|
||
/** | ||
* @param responseString the response body returned by a call to <a href="https://unifiedid.com/docs/endpoints/post-identity-map">/identity/map</a> | ||
* @param envelope the EnvelopeV2 instance returned by {@link #createEnvelopeForIdentityMapRequest} | ||
* @param identityMapInput the same instance that was passed to {@link #createEnvelopeForIdentityMapRequest}. | ||
* @return an IdentityMapV3Response instance | ||
*/ | ||
public IdentityMapV3Response createIdentityMapResponse(String responseString, EnvelopeV2 envelope, IdentityMapV3Input identityMapInput) { | ||
String decryptedResponseString = uid2Helper.decrypt(responseString, envelope.getNonce()); | ||
return new IdentityMapV3Response(decryptedResponseString, identityMapInput); | ||
} | ||
|
||
private final Uid2Helper uid2Helper; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package com.uid2.client; | ||
|
||
import com.google.gson.annotations.SerializedName; | ||
|
||
import java.util.*; | ||
|
||
public class IdentityMapV3Input { | ||
/** | ||
* @param emails a list of normalized or unnormalized email addresses | ||
* @return a IdentityMapV3Input instance, to be used in {@link IdentityMapV3Helper#createEnvelopeForIdentityMapRequest} | ||
*/ | ||
public static IdentityMapV3Input fromEmails(List<String> emails) { | ||
return new IdentityMapV3Input().withEmails(emails); | ||
} | ||
|
||
/** | ||
* @param hashedEmails a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-hash-encoding">hashed</a> email address | ||
* @return an IdentityMapV3Input instance | ||
*/ | ||
public static IdentityMapV3Input fromHashedEmails(List<String> hashedEmails) { | ||
return new IdentityMapV3Input().withHashedEmails(hashedEmails); | ||
} | ||
|
||
/** | ||
* @param phones a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> phone number | ||
* @return an IdentityMapV3Input instance | ||
*/ | ||
public static IdentityMapV3Input fromPhones(List<String> phones) { | ||
return new IdentityMapV3Input().withPhones(phones); | ||
} | ||
|
||
/** | ||
* @param hashedPhones a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-hash-encoding">hashed</a> phone number | ||
* @return an IdentityMapV3Input instance | ||
*/ | ||
public static IdentityMapV3Input fromHashedPhones(List<String> hashedPhones) { | ||
return new IdentityMapV3Input().withHashedPhones(hashedPhones); | ||
} | ||
|
||
// Transient as this should not be part of the serialized JSON payload we send to UID2 Operator | ||
private transient final Map<String, List<String>> hashedDiiToRawDii = new HashMap<>(); | ||
|
||
@SerializedName("email_hash") | ||
private final List<Identity> hashedEmails = new ArrayList<>(); | ||
|
||
@SerializedName("phone_hash") | ||
private final List<Identity> hashedPhones = new ArrayList<>(); | ||
|
||
public IdentityMapV3Input() {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want/need a public ctor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from memory, might be to help consumers of the SDK to write their own tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also for cases where clients have a mix of different DII types, which we now support.
|
||
|
||
/** | ||
* @param hashedEmails a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-hash-encoding">hashed</a> email address | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withHashedEmails(List<String> hashedEmails) { | ||
for (String hashedEmail : hashedEmails) { | ||
withHashedEmail(hashedEmail); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* @param hashedEmail a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#email-address-hash-encoding">hashed</a> email address | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withHashedEmail(String hashedEmail) { | ||
this.hashedEmails.add(new Identity(hashedEmail)); | ||
addToDiiMappings(hashedEmail, hashedEmail); | ||
return this; | ||
} | ||
|
||
/** | ||
* @param hashedPhones a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-hash-encoding">hashed</a> phone number | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withHashedPhones(List<String> hashedPhones) { | ||
for (String hashedPhone : hashedPhones) { | ||
withHashedPhone(hashedPhone); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* @param hashedPhone a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> and <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-hash-encoding">hashed</a> phone number | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withHashedPhone(String hashedPhone) { | ||
this.hashedPhones.add(new Identity(hashedPhone)); | ||
addToDiiMappings(hashedPhone, hashedPhone); | ||
return this; | ||
} | ||
|
||
/** | ||
* @param emails a list of normalized or unnormalized email addresses | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withEmails(List<String> emails) { | ||
for (String email : emails) { | ||
withEmail(email); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* @param email a normalized or unnormalized email address | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withEmail(String email) { | ||
String hashedEmail = InputUtil.normalizeAndHashEmail(email); | ||
this.hashedEmails.add(new Identity(hashedEmail)); | ||
addToDiiMappings(hashedEmail, email); | ||
return this; | ||
} | ||
|
||
/** | ||
* @param phones a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> phone number | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withPhones(List<String> phones) { | ||
for (String phone : phones) { | ||
withPhone(phone); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* @param phone a <a href="https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization">normalized</a> phone number | ||
* @return this IdentityMapV3Input instance | ||
*/ | ||
public IdentityMapV3Input withPhone(String phone) { | ||
if (!InputUtil.isPhoneNumberNormalized(phone)) { | ||
throw new IllegalArgumentException("phone number is not normalized: " + phone); | ||
} | ||
|
||
String hashedPhone = InputUtil.getBase64EncodedHash(phone); | ||
this.hashedPhones.add(new Identity(hashedPhone)); | ||
addToDiiMappings(hashedPhone, phone); | ||
return this; | ||
|
||
} | ||
|
||
List<String> getInputDiis(String identityType, int i) { | ||
return hashedDiiToRawDii.get(getHashedDii(identityType, i)); | ||
} | ||
|
||
private void addToDiiMappings(String hashedDii, String rawDii) { | ||
hashedDiiToRawDii.computeIfAbsent(hashedDii, k -> new ArrayList<>()).add(rawDii); | ||
} | ||
|
||
private String getHashedDii(String identityType, int i) { | ||
switch (identityType) { | ||
case "email_hash": return hashedEmails.get(i).identity; | ||
case "phone_hash": return hashedPhones.get(i).identity; | ||
} | ||
throw new Uid2Exception("Unexpected identity type: " + identityType); | ||
} | ||
|
||
|
||
private static class Identity { | ||
@SerializedName("i") | ||
private final String identity; | ||
|
||
public Identity(String value) { | ||
this.identity = value; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package com.uid2.client; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.annotations.SerializedName; | ||
|
||
import java.time.Instant; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class IdentityMapV3Response { | ||
IdentityMapV3Response(String response, IdentityMapV3Input identityMapInput) { | ||
ApiResponse apiResponse = new Gson().fromJson(response, ApiResponse.class); | ||
status = apiResponse.status; | ||
|
||
if (!isSuccess()) { | ||
throw new Uid2Exception("Got unexpected identity map status: " + status); | ||
} | ||
|
||
populateIdentities(apiResponse.body, identityMapInput); | ||
} | ||
|
||
private void populateIdentities(Map<String, List<ApiIdentity>> apiResponse, IdentityMapV3Input identityMapInput) { | ||
for (Map.Entry<String, List<ApiIdentity>> identitiesForType : apiResponse.entrySet()) { | ||
populateIdentitiesForType(identityMapInput, identitiesForType.getKey(), identitiesForType.getValue()); | ||
} | ||
} | ||
|
||
private void populateIdentitiesForType(IdentityMapV3Input identityMapInput, String identityType, List<ApiIdentity> identities) { | ||
for (int i = 0; i < identities.size(); i++) { | ||
ApiIdentity apiIdentity = identities.get(i); | ||
List<String> inputDiis = identityMapInput.getInputDiis(identityType, i); | ||
for (String inputDii : inputDiis) { | ||
if (apiIdentity.error == null) { | ||
mappedIdentities.put(inputDii, new MappedIdentity(apiIdentity)); | ||
} else { | ||
unmappedIdentities.put(inputDii, new UnmappedIdentity(apiIdentity.error)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public boolean isSuccess() { | ||
return "success".equals(status); | ||
} | ||
|
||
public static class ApiResponse { | ||
@SerializedName("status") | ||
public String status; | ||
|
||
@SerializedName("body") | ||
public Map<String, List<ApiIdentity>> body; | ||
} | ||
|
||
public static class ApiIdentity { | ||
@SerializedName("u") | ||
public String currentUid; | ||
|
||
@SerializedName("p") | ||
public String previousUid; | ||
|
||
@SerializedName("r") | ||
public Long refreshFromSeconds; | ||
|
||
@SerializedName("e") | ||
public String error; | ||
} | ||
|
||
public static class MappedIdentity { | ||
public MappedIdentity(String currentUid, String previousUid, Instant refreshFrom) { | ||
this.currentUid = currentUid; | ||
this.previousUid = previousUid; | ||
this.refreshFrom = refreshFrom; | ||
} | ||
|
||
public MappedIdentity(ApiIdentity apiIdentity) { | ||
this(apiIdentity.currentUid, apiIdentity.previousUid, Instant.ofEpochSecond(apiIdentity.refreshFromSeconds)); | ||
} | ||
|
||
private final String currentUid; | ||
private final String previousUid; | ||
private final Instant refreshFrom; | ||
|
||
public String getCurrentRawUid() { | ||
return currentUid; | ||
} | ||
|
||
public String getPreviousRawUid() { | ||
return previousUid; | ||
} | ||
|
||
public Instant getRefreshFrom() { | ||
return refreshFrom; | ||
} | ||
} | ||
|
||
public static class UnmappedIdentity { | ||
public UnmappedIdentity(String reason) | ||
{ | ||
this.reason = UnmappedIdentityReason.fromString(reason); | ||
this.rawReason = reason; | ||
} | ||
|
||
public UnmappedIdentityReason getReason() { | ||
return reason; | ||
} | ||
|
||
public String getRawReason() { | ||
return rawReason; | ||
} | ||
|
||
private final UnmappedIdentityReason reason; | ||
|
||
private final String rawReason; | ||
} | ||
|
||
public HashMap<String, MappedIdentity> getMappedIdentities() { | ||
return new HashMap<>(mappedIdentities); | ||
} | ||
|
||
public HashMap<String, UnmappedIdentity> getUnmappedIdentities() { | ||
return new HashMap<>(unmappedIdentities); | ||
} | ||
|
||
private final String status; | ||
private final HashMap<String, MappedIdentity> mappedIdentities = new HashMap<>(); | ||
private final HashMap<String, UnmappedIdentity> unmappedIdentities = new HashMap<>(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.uid2.client; | ||
|
||
|
||
public enum UnmappedIdentityReason { | ||
OPTOUT, | ||
INVALID_IDENTIFIER, | ||
UNKNOWN; | ||
|
||
public static UnmappedIdentityReason fromString(String reason) { | ||
if (reason.equals("optout")) { | ||
return OPTOUT; | ||
} | ||
if (reason.equals("invalid identifier")) { | ||
return INVALID_IDENTIFIER; | ||
} | ||
|
||
return UNKNOWN; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.