1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.adservices.service.measurement.registration; 17 18 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA; 19 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_DEDUPLICATION_KEYS_PER_REGISTRATION; 20 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION; 21 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA; 22 23 import android.annotation.NonNull; 24 import android.content.Context; 25 import android.net.Uri; 26 27 import com.android.adservices.LogUtil; 28 import com.android.adservices.data.enrollment.EnrollmentDao; 29 import com.android.adservices.service.Flags; 30 import com.android.adservices.service.FlagsFactory; 31 import com.android.adservices.service.common.AllowLists; 32 import com.android.adservices.service.measurement.AttributionConfig; 33 import com.android.adservices.service.measurement.EventSurfaceType; 34 import com.android.adservices.service.measurement.MeasurementHttpClient; 35 import com.android.adservices.service.measurement.Trigger; 36 import com.android.adservices.service.measurement.XNetworkData; 37 import com.android.adservices.service.measurement.util.BaseUriExtractor; 38 import com.android.adservices.service.measurement.util.Enrollment; 39 import com.android.adservices.service.measurement.util.Filter; 40 import com.android.adservices.service.measurement.util.UnsignedLong; 41 import com.android.adservices.service.measurement.util.Web; 42 import com.android.adservices.service.stats.AdServicesLogger; 43 import com.android.adservices.service.stats.AdServicesLoggerImpl; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import org.json.JSONArray; 47 import org.json.JSONException; 48 import org.json.JSONObject; 49 50 import java.io.IOException; 51 import java.net.HttpURLConnection; 52 import java.net.MalformedURLException; 53 import java.net.URL; 54 import java.net.URLConnection; 55 import java.util.HashSet; 56 import java.util.Iterator; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Optional; 60 import java.util.Set; 61 62 /** 63 * Download and decode Trigger registration. 64 * 65 * @hide 66 */ 67 public class AsyncTriggerFetcher { 68 69 private final MeasurementHttpClient mNetworkConnection = new MeasurementHttpClient(); 70 private final EnrollmentDao mEnrollmentDao; 71 private final Flags mFlags; 72 private final AdServicesLogger mLogger; 73 private final Context mContext; 74 AsyncTriggerFetcher(Context context)75 public AsyncTriggerFetcher(Context context) { 76 this( 77 context, 78 EnrollmentDao.getInstance(context), 79 FlagsFactory.getFlags(), 80 AdServicesLoggerImpl.getInstance()); 81 } 82 83 @VisibleForTesting AsyncTriggerFetcher( Context context, EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger)84 public AsyncTriggerFetcher( 85 Context context, EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger) { 86 mContext = context; 87 mEnrollmentDao = enrollmentDao; 88 mFlags = flags; 89 mLogger = logger; 90 } 91 92 /** 93 * Parse a {@code Trigger}, given response headers, adding the {@code Trigger} to a given list. 94 */ 95 @VisibleForTesting parseTrigger( AsyncRegistration asyncRegistration, String enrollmentId, Map<String, List<String>> headers, AsyncFetchStatus asyncFetchStatus)96 public Optional<Trigger> parseTrigger( 97 AsyncRegistration asyncRegistration, 98 String enrollmentId, 99 Map<String, List<String>> headers, 100 AsyncFetchStatus asyncFetchStatus) { 101 boolean arDebugPermission = asyncRegistration.getDebugKeyAllowed(); 102 LogUtil.d("Trigger ArDebug permission enabled %b", arDebugPermission); 103 Trigger.Builder builder = new Trigger.Builder(); 104 builder.setEnrollmentId(enrollmentId); 105 builder.setAttributionDestination( 106 getAttributionDestination( 107 asyncRegistration.getTopOrigin(), asyncRegistration.getType())); 108 builder.setRegistrant(asyncRegistration.getRegistrant()); 109 builder.setAdIdPermission(asyncRegistration.hasAdIdPermission()); 110 builder.setArDebugPermission(arDebugPermission); 111 builder.setDestinationType( 112 asyncRegistration.isWebRequest() ? EventSurfaceType.WEB : EventSurfaceType.APP); 113 builder.setTriggerTime(asyncRegistration.getRequestTime()); 114 Optional<Uri> registrationUriOrigin = 115 Web.originAndScheme(asyncRegistration.getRegistrationUri()); 116 if (!registrationUriOrigin.isPresent()) { 117 LogUtil.d( 118 "AsyncTriggerFetcher: " 119 + "Invalid or empty registration uri - " 120 + asyncRegistration.getRegistrationUri()); 121 return Optional.empty(); 122 } 123 builder.setRegistrationOrigin(registrationUriOrigin.get()); 124 125 builder.setPlatformAdId( 126 FetcherUtil.getEncryptedPlatformAdIdIfPresent(asyncRegistration, enrollmentId)); 127 128 List<String> field = 129 headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 130 if (field == null || field.size() != 1) { 131 LogUtil.d( 132 "AsyncTriggerFetcher: " 133 + "Invalid " 134 + TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER 135 + " header."); 136 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_ERROR); 137 return Optional.empty(); 138 } 139 try { 140 String eventTriggerData = new JSONArray().toString(); 141 JSONObject json = new JSONObject(field.get(0)); 142 if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) { 143 Optional<String> validEventTriggerData = 144 getValidEventTriggerData( 145 json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA)); 146 if (!validEventTriggerData.isPresent()) { 147 asyncFetchStatus.setEntityStatus( 148 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 149 return Optional.empty(); 150 } 151 eventTriggerData = validEventTriggerData.get(); 152 } 153 builder.setEventTriggers(eventTriggerData); 154 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) { 155 Optional<String> validAggregateTriggerData = 156 getValidAggregateTriggerData( 157 json.getJSONArray(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)); 158 if (!validAggregateTriggerData.isPresent()) { 159 asyncFetchStatus.setEntityStatus( 160 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 161 return Optional.empty(); 162 } 163 builder.setAggregateTriggerData(validAggregateTriggerData.get()); 164 } 165 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_VALUES)) { 166 if (!isValidAggregateValues( 167 json.getJSONObject(TriggerHeaderContract.AGGREGATABLE_VALUES))) { 168 asyncFetchStatus.setEntityStatus( 169 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 170 return Optional.empty(); 171 } 172 builder.setAggregateValues( 173 json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES)); 174 } 175 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)) { 176 Optional<String> validAggregateDeduplicationKeysString = 177 getValidAggregateDuplicationKeysString( 178 json.getJSONArray( 179 TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)); 180 if (!validAggregateDeduplicationKeysString.isPresent()) { 181 LogUtil.d("parseTrigger: aggregate deduplication keys are invalid."); 182 asyncFetchStatus.setEntityStatus( 183 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 184 return Optional.empty(); 185 } 186 builder.setAggregateDeduplicationKeys(validAggregateDeduplicationKeysString.get()); 187 } 188 if (!json.isNull(TriggerHeaderContract.FILTERS)) { 189 JSONArray filters = Filter.maybeWrapFilters(json, TriggerHeaderContract.FILTERS); 190 if (!FetcherUtil.areValidAttributionFilters(filters)) { 191 LogUtil.d("parseTrigger: filters are invalid."); 192 asyncFetchStatus.setEntityStatus( 193 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 194 return Optional.empty(); 195 } 196 builder.setFilters(filters.toString()); 197 } 198 if (!json.isNull(TriggerHeaderContract.NOT_FILTERS)) { 199 JSONArray notFilters = 200 Filter.maybeWrapFilters(json, TriggerHeaderContract.NOT_FILTERS); 201 if (!FetcherUtil.areValidAttributionFilters(notFilters)) { 202 LogUtil.d("parseTrigger: not-filters are invalid."); 203 asyncFetchStatus.setEntityStatus( 204 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 205 return Optional.empty(); 206 } 207 builder.setNotFilters(notFilters.toString()); 208 } 209 if (!json.isNull(TriggerHeaderContract.DEBUG_REPORTING)) { 210 builder.setIsDebugReporting(json.optBoolean(TriggerHeaderContract.DEBUG_REPORTING)); 211 } 212 if (!json.isNull(TriggerHeaderContract.DEBUG_KEY)) { 213 try { 214 builder.setDebugKey( 215 new UnsignedLong(json.getString(TriggerHeaderContract.DEBUG_KEY))); 216 } catch (NumberFormatException e) { 217 LogUtil.e(e, "Parsing trigger debug key failed"); 218 } 219 } 220 if (mFlags.getMeasurementEnableXNA() 221 && !json.isNull(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)) { 222 if (!isValidXNetworkKeyMapping( 223 json.getJSONObject(TriggerHeaderContract.X_NETWORK_KEY_MAPPING))) { 224 LogUtil.d("parseTrigger: adtech bit mapping is invalid."); 225 } else { 226 builder.setAdtechBitMapping( 227 json.getString(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)); 228 } 229 } 230 if (mFlags.getMeasurementEnableXNA() 231 && isXnaAllowedForTriggerRegistrant( 232 asyncRegistration.getRegistrant(), asyncRegistration.getType()) 233 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_CONFIG)) { 234 String attributionConfigsString = 235 extractValidAttributionConfigs( 236 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_CONFIG)); 237 builder.setAttributionConfig(attributionConfigsString); 238 } 239 240 String enrollmentBlockList = 241 mFlags.getMeasurementPlatformDebugAdIdMatchingEnrollmentBlocklist(); 242 Set<String> blockedEnrollmentsString = 243 new HashSet<>(AllowLists.splitAllowList(enrollmentBlockList)); 244 if (!AllowLists.doesAllowListAllowAll(enrollmentBlockList) 245 && !blockedEnrollmentsString.contains(enrollmentId) 246 && !json.isNull(TriggerHeaderContract.DEBUG_AD_ID)) { 247 builder.setDebugAdId(json.optString(TriggerHeaderContract.DEBUG_AD_ID)); 248 } 249 250 Set<String> allowedEnrollmentsString = 251 new HashSet<>( 252 AllowLists.splitAllowList( 253 mFlags.getMeasurementDebugJoinKeyEnrollmentAllowlist())); 254 if (allowedEnrollmentsString.contains(enrollmentId) 255 && !json.isNull(TriggerHeaderContract.DEBUG_JOIN_KEY)) { 256 builder.setDebugJoinKey(json.optString(TriggerHeaderContract.DEBUG_JOIN_KEY)); 257 } 258 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.SUCCESS); 259 return Optional.of(builder.build()); 260 } catch (JSONException e) { 261 LogUtil.e(e, "Trigger Parsing failed"); 262 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.PARSING_ERROR); 263 return Optional.empty(); 264 } 265 } 266 267 /** Provided a testing hook. */ 268 @NonNull openUrl(@onNull URL url)269 public URLConnection openUrl(@NonNull URL url) throws IOException { 270 return mNetworkConnection.setup(url); 271 } 272 isXnaAllowedForTriggerRegistrant( Uri registrant, AsyncRegistration.RegistrationType registrationType)273 private boolean isXnaAllowedForTriggerRegistrant( 274 Uri registrant, AsyncRegistration.RegistrationType registrationType) { 275 // If the trigger is registered from web context, only allow-listed apps should be able to 276 // parse attribution config. 277 return !AsyncRegistration.RegistrationType.WEB_TRIGGER.equals(registrationType) 278 || AllowLists.isPackageAllowListed( 279 mFlags.getWebContextClientAppAllowList(), registrant.getAuthority()); 280 } 281 282 /** 283 * Fetch a trigger type registration. 284 * 285 * @param asyncRegistration a {@link AsyncRegistration}, a request the record. 286 * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status. 287 */ fetchTrigger( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirect asyncRedirect)288 public Optional<Trigger> fetchTrigger( 289 AsyncRegistration asyncRegistration, 290 AsyncFetchStatus asyncFetchStatus, 291 AsyncRedirect asyncRedirect) { 292 HttpURLConnection urlConnection = null; 293 Map<String, List<String>> headers; 294 // TODO(b/276825561): Fix code duplication between fetchSource & fetchTrigger request flow 295 try { 296 urlConnection = 297 (HttpURLConnection) 298 openUrl(new URL(asyncRegistration.getRegistrationUri().toString())); 299 urlConnection.setRequestMethod("POST"); 300 urlConnection.setInstanceFollowRedirects(false); 301 headers = urlConnection.getHeaderFields(); 302 asyncFetchStatus.setResponseSize(FetcherUtil.calculateHeadersCharactersLength(headers)); 303 int responseCode = urlConnection.getResponseCode(); 304 LogUtil.d("Response code = " + responseCode); 305 if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) { 306 asyncFetchStatus.setResponseStatus( 307 AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE); 308 return Optional.empty(); 309 } 310 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.SUCCESS); 311 } catch (MalformedURLException e) { 312 LogUtil.d(e, "Malformed registration target URL"); 313 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL); 314 return Optional.empty(); 315 } catch (IOException e) { 316 LogUtil.d(e, "Failed to get registration response"); 317 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR); 318 return Optional.empty(); 319 } finally { 320 if (urlConnection != null) { 321 urlConnection.disconnect(); 322 } 323 } 324 325 if (asyncRegistration.shouldProcessRedirects()) { 326 FetcherUtil.parseRedirects(headers).forEach(asyncRedirect::addToRedirects); 327 } 328 329 if (!isTriggerHeaderPresent(headers)) { 330 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_MISSING); 331 return Optional.empty(); 332 } 333 334 Optional<String> enrollmentId = 335 mFlags.isDisableMeasurementEnrollmentCheck() 336 ? Optional.of(Enrollment.FAKE_ENROLLMENT) 337 : Enrollment.getValidEnrollmentId( 338 asyncRegistration.getRegistrationUri(), 339 asyncRegistration.getRegistrant().getAuthority(), 340 mEnrollmentDao, 341 mContext, 342 mFlags); 343 if (enrollmentId.isEmpty()) { 344 LogUtil.d( 345 "fetchTrigger: Valid enrollment id not found. Registration URI: %s", 346 asyncRegistration.getRegistrationUri()); 347 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.INVALID_ENROLLMENT); 348 return Optional.empty(); 349 } 350 351 return parseTrigger(asyncRegistration, enrollmentId.get(), headers, asyncFetchStatus); 352 } 353 isTriggerHeaderPresent(Map<String, List<String>> headers)354 private boolean isTriggerHeaderPresent(Map<String, List<String>> headers) { 355 return headers.containsKey( 356 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 357 } 358 getValidEventTriggerData(JSONArray eventTriggerDataArr)359 private static Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) { 360 if (eventTriggerDataArr.length() > MAX_ATTRIBUTION_EVENT_TRIGGER_DATA) { 361 LogUtil.d( 362 "Event trigger data list has more entries than permitted. %s", 363 eventTriggerDataArr.length()); 364 return Optional.empty(); 365 } 366 JSONArray validEventTriggerData = new JSONArray(); 367 for (int i = 0; i < eventTriggerDataArr.length(); i++) { 368 JSONObject validEventTriggerDatum = new JSONObject(); 369 try { 370 JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i); 371 // Treat invalid trigger data, priority and deduplication key as if they were not 372 // set. 373 UnsignedLong triggerData = new UnsignedLong(0L); 374 if (!eventTriggerDatum.isNull("trigger_data")) { 375 try { 376 triggerData = new UnsignedLong(eventTriggerDatum.getString("trigger_data")); 377 } catch (NumberFormatException e) { 378 LogUtil.d(e, "getValidEventTriggerData: parsing trigger_data failed."); 379 } 380 } 381 validEventTriggerDatum.put("trigger_data", triggerData); 382 if (!eventTriggerDatum.isNull("priority")) { 383 try { 384 validEventTriggerDatum.put( 385 "priority", 386 String.valueOf( 387 Long.parseLong(eventTriggerDatum.getString("priority")))); 388 } catch (NumberFormatException e) { 389 LogUtil.d(e, "getValidEventTriggerData: parsing priority failed."); 390 } 391 } 392 if (!eventTriggerDatum.isNull("deduplication_key")) { 393 try { 394 validEventTriggerDatum.put( 395 "deduplication_key", 396 new UnsignedLong(eventTriggerDatum.getString("deduplication_key"))); 397 } catch (NumberFormatException e) { 398 LogUtil.d(e, "getValidEventTriggerData: parsing deduplication_key failed."); 399 } 400 } 401 if (!eventTriggerDatum.isNull("filters")) { 402 JSONArray filters = Filter.maybeWrapFilters(eventTriggerDatum, "filters"); 403 if (!FetcherUtil.areValidAttributionFilters(filters)) { 404 LogUtil.d("getValidEventTriggerData: filters are invalid."); 405 return Optional.empty(); 406 } 407 validEventTriggerDatum.put("filters", filters); 408 } 409 if (!eventTriggerDatum.isNull("not_filters")) { 410 JSONArray notFilters = 411 Filter.maybeWrapFilters(eventTriggerDatum, "not_filters"); 412 if (!FetcherUtil.areValidAttributionFilters(notFilters)) { 413 LogUtil.d("getValidEventTriggerData: not-filters are invalid."); 414 return Optional.empty(); 415 } 416 validEventTriggerDatum.put("not_filters", notFilters); 417 } 418 validEventTriggerData.put(validEventTriggerDatum); 419 } catch (JSONException e) { 420 LogUtil.d(e, "AsyncTriggerFetcher: " + "Parsing event trigger datum JSON failed."); 421 } 422 } 423 return Optional.of(validEventTriggerData.toString()); 424 } 425 getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)426 private static Optional<String> getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr) 427 throws JSONException { 428 if (aggregateTriggerDataArr.length() > MAX_AGGREGATABLE_TRIGGER_DATA) { 429 LogUtil.d( 430 "Aggregate trigger data list has more entries than permitted. %s", 431 aggregateTriggerDataArr.length()); 432 return Optional.empty(); 433 } 434 JSONArray validAggregateTriggerData = new JSONArray(); 435 for (int i = 0; i < aggregateTriggerDataArr.length(); i++) { 436 JSONObject aggregateTriggerData = aggregateTriggerDataArr.getJSONObject(i); 437 String keyPiece = aggregateTriggerData.optString("key_piece"); 438 if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece)) { 439 LogUtil.d("Aggregate trigger data key-piece is invalid. %s", keyPiece); 440 return Optional.empty(); 441 } 442 JSONArray sourceKeys = aggregateTriggerData.optJSONArray("source_keys"); 443 if (sourceKeys == null || sourceKeys.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) { 444 LogUtil.d( 445 "Aggregate trigger data source-keys list failed to parse or has more" 446 + " entries than permitted."); 447 return Optional.empty(); 448 } 449 for (int j = 0; j < sourceKeys.length(); j++) { 450 String key = sourceKeys.optString(j); 451 if (!FetcherUtil.isValidAggregateKeyId(key)) { 452 LogUtil.d("Aggregate trigger data source-key is invalid. %s", key); 453 return Optional.empty(); 454 } 455 } 456 if (!aggregateTriggerData.isNull("filters")) { 457 JSONArray filters = Filter.maybeWrapFilters(aggregateTriggerData, "filters"); 458 if (!FetcherUtil.areValidAttributionFilters(filters)) { 459 LogUtil.d("Aggregate trigger data filters are invalid."); 460 return Optional.empty(); 461 } 462 aggregateTriggerData.put("filters", filters); 463 } 464 if (!aggregateTriggerData.isNull("not_filters")) { 465 JSONArray notFilters = Filter.maybeWrapFilters(aggregateTriggerData, "not_filters"); 466 if (!FetcherUtil.areValidAttributionFilters(notFilters)) { 467 LogUtil.d("Aggregate trigger data not-filters are invalid."); 468 return Optional.empty(); 469 } 470 aggregateTriggerData.put("not_filters", notFilters); 471 } 472 if (!aggregateTriggerData.isNull("x_network_data")) { 473 JSONObject xNetworkDataJson = aggregateTriggerData.getJSONObject("x_network_data"); 474 // This is in order to validate the JSON parsing does not throw exception 475 new XNetworkData.Builder(xNetworkDataJson); 476 } 477 validAggregateTriggerData.put(aggregateTriggerData); 478 } 479 return Optional.of(validAggregateTriggerData.toString()); 480 } 481 isValidAggregateValues(JSONObject aggregateValues)482 private boolean isValidAggregateValues(JSONObject aggregateValues) { 483 if (aggregateValues.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) { 484 LogUtil.d( 485 "Aggregate values have more keys than permitted. %s", aggregateValues.length()); 486 return false; 487 } 488 Iterator<String> ids = aggregateValues.keys(); 489 while (ids.hasNext()) { 490 String id = ids.next(); 491 if (!FetcherUtil.isValidAggregateKeyId(id)) { 492 LogUtil.d("Aggregate values key ID is invalid. %s", id); 493 return false; 494 } 495 } 496 return true; 497 } 498 getValidAggregateDuplicationKeysString( JSONArray aggregateDeduplicationKeys)499 private Optional<String> getValidAggregateDuplicationKeysString( 500 JSONArray aggregateDeduplicationKeys) throws JSONException { 501 JSONArray validAggregateDeduplicationKeys = new JSONArray(); 502 if (aggregateDeduplicationKeys.length() 503 > MAX_AGGREGATE_DEDUPLICATION_KEYS_PER_REGISTRATION) { 504 LogUtil.d( 505 "Aggregate deduplication keys have more keys than permitted. %s", 506 aggregateDeduplicationKeys.length()); 507 return Optional.empty(); 508 } 509 for (int i = 0; i < aggregateDeduplicationKeys.length(); i++) { 510 JSONObject aggregateDedupKey = new JSONObject(); 511 JSONObject deduplication_key = aggregateDeduplicationKeys.getJSONObject(i); 512 513 String deduplicationKey = deduplication_key.optString("deduplication_key"); 514 if (!deduplication_key.isNull("deduplication_key") 515 && FetcherUtil.isValidAggregateDeduplicationKey(deduplicationKey)) { 516 aggregateDedupKey.put("deduplication_key", deduplicationKey); 517 } 518 if (!deduplication_key.isNull("filters")) { 519 JSONArray filters = Filter.maybeWrapFilters(deduplication_key, "filters"); 520 if (!FetcherUtil.areValidAttributionFilters(filters)) { 521 LogUtil.d("Aggregate deduplication key: " + i + " contains invalid filters."); 522 return Optional.empty(); 523 } 524 aggregateDedupKey.put("filters", filters); 525 } 526 if (!deduplication_key.isNull("not_filters")) { 527 JSONArray notFilters = Filter.maybeWrapFilters(deduplication_key, "not_filters"); 528 if (!FetcherUtil.areValidAttributionFilters(notFilters)) { 529 LogUtil.d( 530 "Aggregate deduplication key: " + i + " contains invalid not filters."); 531 return Optional.empty(); 532 } 533 aggregateDedupKey.put("not_filters", notFilters); 534 } 535 validAggregateDeduplicationKeys.put(aggregateDedupKey); 536 } 537 return Optional.of(validAggregateDeduplicationKeys.toString()); 538 } 539 extractValidAttributionConfigs(JSONArray attributionConfigsArray)540 private String extractValidAttributionConfigs(JSONArray attributionConfigsArray) 541 throws JSONException { 542 JSONArray validAttributionConfigsArray = new JSONArray(); 543 for (int i = 0; i < attributionConfigsArray.length(); i++) { 544 AttributionConfig attributionConfig = 545 new AttributionConfig.Builder(attributionConfigsArray.getJSONObject(i)).build(); 546 validAttributionConfigsArray.put(attributionConfig.serializeAsJson()); 547 } 548 return validAttributionConfigsArray.toString(); 549 } 550 isValidXNetworkKeyMapping(JSONObject adTechBitMapping)551 private boolean isValidXNetworkKeyMapping(JSONObject adTechBitMapping) throws JSONException { 552 // TODO: Might need to add logic for keys' and values' lengths. 553 Iterator<String> keys = adTechBitMapping.keys(); 554 while (keys.hasNext()) { 555 String key = keys.next(); 556 String value = adTechBitMapping.optString(key); 557 if (value == null || !value.startsWith("0x")) { 558 return false; 559 } 560 } 561 return true; 562 } 563 getAttributionDestination(Uri destination, AsyncRegistration.RegistrationType registrationType)564 private static Uri getAttributionDestination(Uri destination, 565 AsyncRegistration.RegistrationType registrationType) { 566 return registrationType == AsyncRegistration.RegistrationType.APP_TRIGGER 567 ? BaseUriExtractor.getBaseUri(destination) 568 : destination; 569 } 570 571 private interface TriggerHeaderContract { 572 String HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER = 573 "Attribution-Reporting-Register-Trigger"; 574 String ATTRIBUTION_CONFIG = "attribution_config"; 575 String EVENT_TRIGGER_DATA = "event_trigger_data"; 576 String FILTERS = "filters"; 577 String NOT_FILTERS = "not_filters"; 578 String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data"; 579 String AGGREGATABLE_VALUES = "aggregatable_values"; 580 String AGGREGATABLE_DEDUPLICATION_KEYS = "aggregatable_deduplication_keys"; 581 String DEBUG_KEY = "debug_key"; 582 String DEBUG_REPORTING = "debug_reporting"; 583 String X_NETWORK_KEY_MAPPING = "x_network_key_mapping"; 584 String DEBUG_JOIN_KEY = "debug_join_key"; 585 String DEBUG_AD_ID = "debug_ad_id"; 586 } 587 } 588