From 505772a4b17d94851828ed3afdacf90e70df8734 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 11 Dec 2024 20:26:15 +0530 Subject: [PATCH 1/7] Add API for audience lookup --- src/main/resources/functions/component.xml | 4 ++ .../functions/getCustomAudiences.xml | 49 +++++++++++++++ .../uischema/getCustomAudiences.json | 61 +++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 src/main/resources/functions/getCustomAudiences.xml create mode 100644 src/main/resources/uischema/getCustomAudiences.json diff --git a/src/main/resources/functions/component.xml b/src/main/resources/functions/component.xml index 50e7476..895888c 100644 --- a/src/main/resources/functions/component.xml +++ b/src/main/resources/functions/component.xml @@ -101,5 +101,9 @@ removeUsersFromAudience.xml Remove users from your Custom Audience. + + getCustomAudiences.xml + Returns all custom audiences. + diff --git a/src/main/resources/functions/getCustomAudiences.xml b/src/main/resources/functions/getCustomAudiences.xml new file mode 100644 index 0000000..7fe49fa --- /dev/null +++ b/src/main/resources/functions/getCustomAudiences.xml @@ -0,0 +1,49 @@ + + + + + diff --git a/src/main/resources/uischema/getCustomAudiences.json b/src/main/resources/uischema/getCustomAudiences.json new file mode 100644 index 0000000..814cb9d --- /dev/null +++ b/src/main/resources/uischema/getCustomAudiences.json @@ -0,0 +1,61 @@ +{ + "connectorName": "facebookAds", + "operationName": "getCustomAudiences", + "title": "Get Custom Audiences", + "help": "Returns all the custom audiences.", + "elements": [ + { + "type": "attributeGroup", + "value": { + "groupName": "General", + "elements": [ + { + "type": "attribute", + "value": { + "name": "configRef", + "displayName": "Connection", + "inputType": "connection", + "allowedConnectionTypes": [ + "facebookAds" + ], + "defaultType": "connection.facebookAds", + "defaultValue": "", + "required": "true", + "helpTip": "Connection to be used." + } + }, + { + "type": "attributeGroup", + "value": { + "groupName": "Parameters", + "elements": [ + { + "type": "attribute", + "value": { + "name": "adAccountId", + "displayName": "Ad Account Id", + "inputType": "stringOrExpression", + "defaultValue": "", + "required": "true", + "helpTip": "ID of the ad account." + } + }, + { + "type": "attribute", + "value": { + "name": "fields", + "displayName": "Fields", + "inputType": "stringOrExpression", + "defaultValue": "name,id,operation_status,time_created,time_updated", + "required": "false", + "helpTip": "Fields of the audiences." + } + } + ] + } + } + ] + } + } + ] +} From edc95ae5689f98c21446e91d7e2cb45df19da755 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 11 Dec 2024 21:26:13 +0530 Subject: [PATCH 2/7] Add internally hashing user data --- .connector-store/meta.json | 2 +- README.md | 2 +- gen_resources/config_fb.json | 2 +- pom.xml | 2 +- .../connector/FacebookDataClassMediator.java | 24 + .../ads/connector/FacebookDataProcessor.java | 596 ++++++++++++++++++ .../functions/addUsersToAudience.xml | 22 +- .../functions/removeUsersFromAudience.xml | 20 +- 8 files changed, 660 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataClassMediator.java create mode 100644 src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataProcessor.java diff --git a/.connector-store/meta.json b/.connector-store/meta.json index 3dc250d..8c6e4fa 100644 --- a/.connector-store/meta.json +++ b/.connector-store/meta.json @@ -12,7 +12,7 @@ "type": "Connector", "releases": [ { - "tagName": "v1.0.2", + "tagName": "v1.0.3", "products": [ "MI 4.3.0" ], diff --git a/README.md b/README.md index 7e461f5..3db9585 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ | Connector Version | Supported WSO2 MI Version | |-------------------|---------------------------| -| 1.0.2 | MI 4.3.0 | +| 1.0.3 | MI 4.3.0 | ## Documentation diff --git a/gen_resources/config_fb.json b/gen_resources/config_fb.json index 3661251..4b0bdf5 100644 --- a/gen_resources/config_fb.json +++ b/gen_resources/config_fb.json @@ -9,7 +9,7 @@ "project": { "groupId": "org.wso2.carbon.esb.connector", "artifactId": "facebook.ads", - "version": "1.0.2-SNAPSHOT" + "version": "1.0.3-SNAPSHOT" }, "iconFolderPath": "", "mappers": { diff --git a/pom.xml b/pom.xml index 2277462..44f4f65 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.wso2.integration.connector mi-connector-facebookads - 1.0.2 + 1.0.3 WSO2 Carbon - Facebook Ads Connector http://wso2.org diff --git a/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataClassMediator.java b/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataClassMediator.java new file mode 100644 index 0000000..ccd72e2 --- /dev/null +++ b/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataClassMediator.java @@ -0,0 +1,24 @@ +package org.wso2.carbon.facebook.ads.connector; + +import org.apache.synapse.MessageContext; +import org.apache.synapse.mediators.AbstractMediator; +import org.wso2.carbon.connector.core.util.ConnectorUtils; +import org.json.JSONArray; +import org.json.JSONObject; + +public class FacebookDataClassMediator extends AbstractMediator { + + @Override + public boolean mediate(MessageContext synCtx) { + String jsonString = (String) ConnectorUtils.lookupTemplateParamater(synCtx, "properties"); + if (jsonString == null || jsonString.isEmpty()) { + return true; + } + + JSONArray inputArray = new JSONArray(jsonString); + + JSONObject finalObj = FacebookDataProcessor.processData(inputArray); + synCtx.setProperty("HASHED_PAYLOAD", finalObj.toString()); + return true; + } +} diff --git a/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataProcessor.java b/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataProcessor.java new file mode 100644 index 0000000..1bbaf12 --- /dev/null +++ b/src/main/java/org/wso2/carbon/facebook/ads/connector/FacebookDataProcessor.java @@ -0,0 +1,596 @@ +package org.wso2.carbon.facebook.ads.connector; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +class FacebookDataProcessor { + + private static final Log log = LogFactory.getLog(FacebookDataProcessor.class); + + private static final Set ALLOWED_PII_TYPES = new HashSet<>(Arrays.asList( + "EXTERN_ID", "EMAIL", "PHONE", "GEN", "DOBY", "DOBM", "DOBD", + "LN", "FN", "FI", "CT", "ST", "ZIP", "MADID", "COUNTRY", "DOB" + )); + + private static final Map FIELD_NAME_MAPPING = new HashMap<>(); + private static final Map US_STATE_MAP = new HashMap<>(); + private static final Map COUNTRY_MAP = new HashMap<>(); + + static { + FIELD_NAME_MAPPING.put("extern_id", "EXTERN_ID"); + FIELD_NAME_MAPPING.put("email", "EMAIL"); + FIELD_NAME_MAPPING.put("phone", "PHONE"); + FIELD_NAME_MAPPING.put("gen", "GEN"); + FIELD_NAME_MAPPING.put("doby", "DOBY"); + FIELD_NAME_MAPPING.put("dobm", "DOBM"); + FIELD_NAME_MAPPING.put("dobd", "DOBD"); + FIELD_NAME_MAPPING.put("ln", "LN"); + FIELD_NAME_MAPPING.put("fn", "FN"); + FIELD_NAME_MAPPING.put("fi", "FI"); + FIELD_NAME_MAPPING.put("ct", "CT"); + FIELD_NAME_MAPPING.put("st", "ST"); + FIELD_NAME_MAPPING.put("zip", "ZIP"); + FIELD_NAME_MAPPING.put("madid", "MADID"); + FIELD_NAME_MAPPING.put("country", "COUNTRY"); + FIELD_NAME_MAPPING.put("dob", "DOB"); + + // All US states and DC + US_STATE_MAP.put("alabama", "al"); + US_STATE_MAP.put("alaska", "ak"); + US_STATE_MAP.put("arizona", "az"); + US_STATE_MAP.put("arkansas", "ar"); + US_STATE_MAP.put("california", "ca"); + US_STATE_MAP.put("colorado", "co"); + US_STATE_MAP.put("connecticut", "ct"); + US_STATE_MAP.put("delaware", "de"); + US_STATE_MAP.put("florida", "fl"); + US_STATE_MAP.put("georgia", "ga"); + US_STATE_MAP.put("hawaii", "hi"); + US_STATE_MAP.put("idaho", "id"); + US_STATE_MAP.put("illinois", "il"); + US_STATE_MAP.put("indiana", "in"); + US_STATE_MAP.put("iowa", "ia"); + US_STATE_MAP.put("kansas", "ks"); + US_STATE_MAP.put("kentucky", "ky"); + US_STATE_MAP.put("louisiana", "la"); + US_STATE_MAP.put("maine", "me"); + US_STATE_MAP.put("maryland", "md"); + US_STATE_MAP.put("massachusetts", "ma"); + US_STATE_MAP.put("michigan", "mi"); + US_STATE_MAP.put("minnesota", "mn"); + US_STATE_MAP.put("mississippi", "ms"); + US_STATE_MAP.put("missouri", "mo"); + US_STATE_MAP.put("montana", "mt"); + US_STATE_MAP.put("nebraska", "ne"); + US_STATE_MAP.put("nevada", "nv"); + US_STATE_MAP.put("newhampshire", "nh"); + US_STATE_MAP.put("newjersey", "nj"); + US_STATE_MAP.put("newmexico", "nm"); + US_STATE_MAP.put("newyork", "ny"); + US_STATE_MAP.put("northcarolina", "nc"); + US_STATE_MAP.put("northdakota", "nd"); + US_STATE_MAP.put("ohio", "oh"); + US_STATE_MAP.put("oklahoma", "ok"); + US_STATE_MAP.put("oregon", "or"); + US_STATE_MAP.put("pennsylvania", "pa"); + US_STATE_MAP.put("rhodeisland", "ri"); + US_STATE_MAP.put("southcarolina", "sc"); + US_STATE_MAP.put("southdakota", "sd"); + US_STATE_MAP.put("tennessee", "tn"); + US_STATE_MAP.put("texas", "tx"); + US_STATE_MAP.put("utah", "ut"); + US_STATE_MAP.put("vermont", "vt"); + US_STATE_MAP.put("virginia", "va"); + US_STATE_MAP.put("washington", "wa"); + US_STATE_MAP.put("westvirginia", "wv"); + US_STATE_MAP.put("wisconsin", "wi"); + US_STATE_MAP.put("wyoming", "wy"); + US_STATE_MAP.put("district of columbia", "dc"); + + COUNTRY_MAP.put("unitedstates", "us"); + COUNTRY_MAP.put("us", "us"); + COUNTRY_MAP.put("unitedkingdom", "gb"); + COUNTRY_MAP.put("greatbritain", "gb"); + COUNTRY_MAP.put("china", "cn"); + COUNTRY_MAP.put("canada", "ca"); + COUNTRY_MAP.put("france", "fr"); + COUNTRY_MAP.put("germany", "de"); + } + + static JSONObject processData(JSONArray inputArray) { + Map fieldCounts = new HashMap<>(); + + // Determine maximum occurrences of each field + for (int i = 0; i < inputArray.length(); i++) { + JSONObject entry = inputArray.getJSONObject(i); + Iterator keys = entry.keys(); + while (keys.hasNext()) { + String key = keys.next(); + String mappedKey = FIELD_NAME_MAPPING.getOrDefault(key.toLowerCase().replaceAll("\\.\\d+", ""), + key.toUpperCase().replaceAll("\\.\\d+", "")); + if (key.toLowerCase().contains("uid") || key.toLowerCase().contains("externid")) { + mappedKey = "EXTERN_ID"; + } + if (ALLOWED_PII_TYPES.contains(mappedKey)) { + int currentMax = fieldCounts.getOrDefault(mappedKey, 0); + int keyCount = countOccurrences(entry, key); + fieldCounts.put(mappedKey, Math.max(currentMax, keyCount)); + } else { + log.warn("Ignoring unsupported PII type: " + key); + } + } + } + + // Build schema + List schemaList = new ArrayList<>(); + for (Map.Entry entry : fieldCounts.entrySet()) { + String field = entry.getKey(); + int count = entry.getValue(); + for (int i = 0; i < count; i++) { + schemaList.add(field); + } + } + + Collections.sort(schemaList); + JSONArray schemaArray = new JSONArray(schemaList); + JSONArray dataArray = new JSONArray(); + + // Build data rows + for (int i = 0; i < inputArray.length(); i++) { + JSONObject entry = inputArray.getJSONObject(i); + String country = entry.optString("country", null); + Map fieldOccurrenceCounter = new HashMap<>(); + JSONArray dataRow = new JSONArray(); + + for (String field : schemaList) { + int occurrence = fieldOccurrenceCounter.getOrDefault(field, 0); + fieldOccurrenceCounter.put(field, occurrence + 1); + + String originalField = getOriginalFieldName(field); + String foundValue = getOccurrenceValue(entry, originalField, occurrence, field); + String normalized = normalizeField(field, foundValue, country); + String finalValue = normalized.isEmpty() ? "" : (requiresHashing(field) ? hashIfNotAlreadyHashed(normalized) : normalized); + dataRow.put(finalValue); + } + + dataArray.put(dataRow); + } + + JSONObject payloadObj = new JSONObject(); + payloadObj.put("schema", schemaArray); + payloadObj.put("data", dataArray); + + JSONObject finalObj = new JSONObject(); + finalObj.put("payload", payloadObj); + return finalObj; + } + + private static int countOccurrences(JSONObject entry, String key) { + int keyCount = 0; + String baseKey = key.replaceAll("\\.\\d+", ""); + while (true) { + String fieldKey = baseKey + (keyCount == 0 ? "" : "." + keyCount); + if (entry.has(fieldKey)) { + keyCount++; + } else { + break; + } + } + return keyCount; + } + + private static String getOccurrenceValue(JSONObject entry, String originalField, int occurrence, String piiType) { + if ("EXTERN_ID".equals(piiType)) { + // Gather all fields that could represent EXTERN_ID: extern_id, uid, externid + List externIdFields = new ArrayList<>(); + for (Object objKey : entry.keySet()) { + String key = (String) objKey; + String lower = key.toLowerCase(); + String baseKey = key.replaceAll("\\.\\d+", ""); + if (lower.contains("uid") || lower.contains("externid") || lower.contains("extern_id")) { + externIdFields.add(key); + } + } + // Sort them to ensure deterministic order (by base key and numeric index) + externIdFields.sort((a, b) -> { + String baseA = a.replaceAll("\\.\\d+", ""); + String baseB = b.replaceAll("\\.\\d+", ""); + int cmp = baseA.compareTo(baseB); + if (cmp == 0) { + // Compare occurrence suffixes if any + int idxA = getSuffixIndex(a); + int idxB = getSuffixIndex(b); + return Integer.compare(idxA, idxB); + } + return cmp; + }); + + int foundIndex = 0; + for (String f : externIdFields) { + // Count occurrences of each matched field + // If a field base name matches multiple occurrences, we handle them too + String baseF = f.replaceAll("\\.\\d+", ""); + int maxCount = countOccurrences(entry, baseF); + for (int i = 0; i < maxCount; i++) { + String candidate = baseF + (i == 0 ? "" : "." + i); + if (!entry.has(candidate)) break; + if (foundIndex == occurrence) { + return entry.optString(candidate, ""); + } + foundIndex++; + } + } + + return ""; + } else { + int foundIndex = 0; + for (int attempt = 0; ; attempt++) { + String candidate = originalField + (attempt == 0 ? "" : "." + attempt); + if (!entry.has(candidate)) break; + if (foundIndex == occurrence) { + return entry.optString(candidate, ""); + } + foundIndex++; + } + return ""; + } + } + + private static int getSuffixIndex(String key) { + Matcher m = Pattern.compile("\\.(\\d+)$").matcher(key); + if (m.find()) { + try { + return Integer.parseInt(m.group(1)); + } catch (NumberFormatException e) { + // ignore + } + } + return 0; + } + + private static String getOccurrenceValue(JSONObject entry, String originalField, int occurrence) { + return getOccurrenceValue(entry, originalField, occurrence, ""); + } + + private static String getOriginalFieldName(String piiType) { + for (Map.Entry entry : FIELD_NAME_MAPPING.entrySet()) { + if (entry.getValue().equals(piiType)) { + return entry.getKey(); + } + } + return piiType.toLowerCase(); + } + + private static boolean requiresHashing(String fieldName) { + return !fieldName.equalsIgnoreCase("MADID") && + !fieldName.equalsIgnoreCase("FI") && + !fieldName.equalsIgnoreCase("PAGE_SCOPED_USER_ID"); + } + + private static void printNormalization(String fieldName, String originalValue, String normalizedValue) { + System.out.println("Normalizing field: " + fieldName); + System.out.println("original = " + originalValue); + System.out.println("normalized = " + normalizedValue); + } + + private static Map parsePhoneMeta(String val) { + Map meta = new HashMap<>(); + Pattern p = Pattern.compile("(alreadyHasPhoneCode|countryCode|phoneCode|phoneNumber)\\s*:\\s*([^;]+)"); + Matcher m = p.matcher(val); + while (m.find()) { + String key = m.group(1).trim(); + String rawValue = m.group(2).trim(); + if (rawValue.startsWith("'") && rawValue.endsWith("'")) { + rawValue = rawValue.substring(1, rawValue.length() - 1); + } else if (rawValue.startsWith("\"") && rawValue.endsWith("\"")) { + rawValue = rawValue.substring(1, rawValue.length() - 1); + } + meta.put(key, rawValue); + } + return meta; + } + + private static String normalizePhone(String val, String country) { + Map meta = parsePhoneMeta(val); + if (meta.isEmpty()) { + val = val.replaceAll("\\D+", ""); + if ("us".equalsIgnoreCase(country)) { + val = val.replaceFirst("^0+", ""); + if (!val.isEmpty() && !val.startsWith("1")) { + val = "1" + val; + } + } + return val; + } else { + String alreadyHasPhoneCodeStr = meta.get("alreadyHasPhoneCode"); + String countryCode = meta.get("countryCode"); + String phoneCode = meta.get("phoneCode"); + String phoneNumber = meta.get("phoneNumber"); + + if (phoneNumber == null) { + val = val.replaceAll("\\D+", ""); + if ("us".equalsIgnoreCase(country)) { + val = val.replaceFirst("^0+", ""); + if (!val.isEmpty() && !val.startsWith("1")) { + val = "1" + val; + } + } + return val; + } + + phoneNumber = phoneNumber.replaceAll("\\D+", ""); + boolean alreadyHasPhoneCode = alreadyHasPhoneCodeStr != null && alreadyHasPhoneCodeStr.equalsIgnoreCase("true"); + + if (!alreadyHasPhoneCode) { + if (phoneCode != null && !phoneCode.isEmpty()) { + phoneCode = phoneCode.replaceAll("\\D+", ""); + phoneNumber = phoneCode + phoneNumber; + } else if ("us".equalsIgnoreCase(countryCode)) { + phoneNumber = phoneNumber.replaceFirst("^0+", ""); + if (!phoneNumber.isEmpty() && !phoneNumber.startsWith("1")) { + phoneNumber = "1" + phoneNumber; + } + } + } + + return phoneNumber; + } + } + + private static String normalizeCity(String val) { + return val.replaceAll("[^a-z]", ""); + } + + private static String normalizeState(String val, String country) { + String clean = val.replaceAll("[^a-z]", ""); + if ("us".equalsIgnoreCase(country)) { + if (clean.length() == 2 && US_STATE_MAP.containsValue(clean)) { + return clean; + } + String stateCode = US_STATE_MAP.get(clean); + if (stateCode != null) { + return stateCode; + } + } + return clean; + } + + private static String normalizeGender(String val) { + Set maleSynonyms = new HashSet<>(Arrays.asList("m", "male", "man", "boy")); + Set femaleSynonyms = new HashSet<>(Arrays.asList("f", "female", "woman", "girl")); + + if (maleSynonyms.contains(val)) { + return "m"; + } else if (femaleSynonyms.contains(val)) { + return "f"; + } + return "m"; + } + + private static String normalizeCountry(String val) { + val = val.replaceAll("[^a-z]", ""); + String countryCode = COUNTRY_MAP.get(val); + if (countryCode != null) { + return countryCode; + } + return val; + } + + private static String parseDOBToYYYYMMDD(String val) { + String[] parts = val.split("[/\\-–]+"); + if (parts.length < 3) { + return val; + } + + int[] nums = new int[3]; + for (int i = 0; i < 3; i++) { + try { + nums[i] = Integer.parseInt(parts[i].replaceAll("\\D", "")); + } catch (NumberFormatException e) { + return val; + } + } + + int a = nums[0], b = nums[1], c = nums[2]; + int year, month, day; + + if (String.valueOf(c).length() == 4) { + year = c; + if (a <= 12 && b <= 31) { + month = a; day = b; + } else if (b <= 12 && a <=31) { + month = b; day = a; + } else { + return val; + } + } else if (String.valueOf(a).length() == 4) { + year = a; + if (b <= 12 && c <=31) { + month = b; day = c; + } else if (c <=12 && b <=31) { + month = c; day = b; + } else { + return val; + } + } else if (String.valueOf(b).length() == 4) { + year = b; + if (a <= 12 && c <=31) { + month = a; day = c; + } else if (c <=12 && a <=31) { + month = c; day = a; + } else { + return val; + } + } else { + return val; + } + + if (month < 1 || month > 12 || day < 1 || day > 31) { + return val; + } + return String.format("%04d%02d%02d", year, month, day); + } + + private static String normalizeDOB(String val) { + return parseDOBToYYYYMMDD(val); + } + + private static String normalizeDOBY(String val) { + if (val.matches("\\d{2}")) { + int year = Integer.parseInt(val); + if (year < 50) { + year += 2000; + } else { + year += 1900; + } + val = String.valueOf(year); + } + return val; + } + + private static String normalizeDOBM(String val) { + if (val.matches("\\d+")) { + int month = Integer.parseInt(val); + if (month < 1 || month > 12) { + return ""; + } + return String.format("%02d", month); + } + return ""; + } + + private static String normalizeDOBD(String val) { + if (val.matches("\\d+")) { + int day = Integer.parseInt(val); + if (day < 1 || day > 31) { + return ""; + } + return String.format("%02d", day); + } + return ""; + } + + private static String normalizeField(String fieldName, String value, String country) { + if (value == null) { + printNormalization(fieldName, "null", ""); + return ""; + } + + String originalValue = value; + String val = value.trim().toLowerCase(); + + switch (fieldName) { + case "EMAIL": + printNormalization(fieldName, originalValue, val); + return val; + + case "PHONE": + val = normalizePhone(val, country); + printNormalization(fieldName, originalValue, val); + return val; + + case "FN": + case "LN": + val = val.replaceAll("[^\\p{L}]", ""); + printNormalization(fieldName, originalValue, val); + return val; + + case "ZIP": + val = val.replaceAll("\\s+", ""); + if ("us".equalsIgnoreCase(country)) { + val = val.replaceAll("[^0-9]", ""); + if (val.length() > 5) { + val = val.substring(0, 5); + } + } + printNormalization(fieldName, originalValue, val); + return val; + + case "CT": + val = normalizeCity(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "ST": + val = normalizeState(val, country); + printNormalization(fieldName, originalValue, val); + return val; + + case "COUNTRY": + val = normalizeCountry(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "DOB": + val = normalizeDOB(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "DOBY": + val = normalizeDOBY(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "DOBM": + val = normalizeDOBM(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "DOBD": + val = normalizeDOBD(val); + printNormalization(fieldName, originalValue, val); + return val; + + case "GEN": + val = normalizeGender(val); + printNormalization(fieldName, originalValue, val); + return val; + + default: + printNormalization(fieldName, originalValue, val); + return val; + } + } + + private static String hashString(String input) { + if (input == null || input.isEmpty()) return ""; + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedhash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(encodedhash); + } catch (NoSuchAlgorithmException e) { + log.error("Error hashing string", e); + return ""; + } + } + + private static String bytesToHex(byte[] hash) { + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } + + private static String hashIfNotAlreadyHashed(String input) { + if (input.matches("^[0-9a-f]{64}$")) { + return input; + } + return hashString(input); + } + + static void unhashAndPrintFinalPayload(JSONObject finalObj) { + System.out.println(finalObj.toString(4)); + } +} diff --git a/src/main/resources/functions/addUsersToAudience.xml b/src/main/resources/functions/addUsersToAudience.xml index 045d6d5..bb4ab6f 100644 --- a/src/main/resources/functions/addUsersToAudience.xml +++ b/src/main/resources/functions/addUsersToAudience.xml @@ -19,33 +19,47 @@