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.registration.AsyncFetchStatus.EntityStatus; 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT; 22 23 import android.adservices.ondevicepersonalization.OnDevicePersonalizationSystemEventManager; 24 import android.annotation.NonNull; 25 import android.content.Context; 26 import android.net.Uri; 27 28 import com.android.adservices.LoggerFactory; 29 import com.android.adservices.data.enrollment.EnrollmentDao; 30 import com.android.adservices.data.measurement.DatastoreManager; 31 import com.android.adservices.data.measurement.DatastoreManagerFactory; 32 import com.android.adservices.errorlogging.ErrorLogUtil; 33 import com.android.adservices.service.Flags; 34 import com.android.adservices.service.FlagsFactory; 35 import com.android.adservices.service.common.AllowLists; 36 import com.android.adservices.service.common.WebAddresses; 37 import com.android.adservices.service.measurement.AttributionConfig; 38 import com.android.adservices.service.measurement.EventSurfaceType; 39 import com.android.adservices.service.measurement.MeasurementHttpClient; 40 import com.android.adservices.service.measurement.Trigger; 41 import com.android.adservices.service.measurement.TriggerSpecs; 42 import com.android.adservices.service.measurement.XNetworkData; 43 import com.android.adservices.service.measurement.aggregation.AggregatableKeyValue.AggregatableKeyValueContract; 44 import com.android.adservices.service.measurement.aggregation.AggregatableNamedBudget.NamedBudgetContract; 45 import com.android.adservices.service.measurement.aggregation.AggregatableValuesConfig.AggregatableValuesConfigContract; 46 import com.android.adservices.service.measurement.ondevicepersonalization.IOdpDelegationWrapper; 47 import com.android.adservices.service.measurement.ondevicepersonalization.NoOdpDelegationWrapper; 48 import com.android.adservices.service.measurement.ondevicepersonalization.OdpDelegationWrapperImpl; 49 import com.android.adservices.service.measurement.reporting.DebugReportApi; 50 import com.android.adservices.service.measurement.util.BaseUriExtractor; 51 import com.android.adservices.service.measurement.util.Enrollment; 52 import com.android.adservices.service.measurement.util.Filter; 53 import com.android.adservices.service.measurement.util.Filter.FilterContract; 54 import com.android.adservices.service.measurement.util.UnsignedLong; 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.modules.utils.build.SdkLevel; 57 58 import org.json.JSONArray; 59 import org.json.JSONException; 60 import org.json.JSONObject; 61 62 import java.io.IOException; 63 import java.math.BigDecimal; 64 import java.math.BigInteger; 65 import java.net.HttpURLConnection; 66 import java.net.MalformedURLException; 67 import java.net.URL; 68 import java.net.URLConnection; 69 import java.util.HashSet; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.Locale; 73 import java.util.Map; 74 import java.util.Optional; 75 import java.util.Set; 76 import java.util.UUID; 77 78 /** 79 * Download and decode Trigger registration. 80 * 81 * @hide 82 */ 83 public class AsyncTriggerFetcher { 84 85 private final MeasurementHttpClient mNetworkConnection; 86 private final EnrollmentDao mEnrollmentDao; 87 private final Flags mFlags; 88 private final Context mContext; 89 private final IOdpDelegationWrapper mOdpWrapper; 90 private final DatastoreManager mDatastoreManager; 91 private final DebugReportApi mDebugReportApi; 92 private static final long ONE_BYTE = (long) Math.pow(2, 8); 93 AsyncTriggerFetcher(Context context)94 public AsyncTriggerFetcher(Context context) { 95 this( 96 context, 97 EnrollmentDao.getInstance(), 98 FlagsFactory.getFlags(), 99 getOdpDelegationManager(context, FlagsFactory.getFlags()), 100 DatastoreManagerFactory.getDatastoreManager(), 101 new DebugReportApi(context, FlagsFactory.getFlags())); 102 } 103 104 @VisibleForTesting AsyncTriggerFetcher( Context context, EnrollmentDao enrollmentDao, Flags flags, IOdpDelegationWrapper odpWrapper, DatastoreManager datastoreManager, DebugReportApi debugReportApi)105 public AsyncTriggerFetcher( 106 Context context, 107 EnrollmentDao enrollmentDao, 108 Flags flags, 109 IOdpDelegationWrapper odpWrapper, 110 DatastoreManager datastoreManager, 111 DebugReportApi debugReportApi) { 112 mContext = context; 113 mEnrollmentDao = enrollmentDao; 114 mFlags = flags; 115 mNetworkConnection = new MeasurementHttpClient(context); 116 mDatastoreManager = datastoreManager; 117 mDebugReportApi = debugReportApi; 118 mOdpWrapper = odpWrapper; 119 } 120 121 /** 122 * Parse a {@code Trigger}, given response headers, adding the {@code Trigger} to a given list. 123 */ 124 @VisibleForTesting parseTrigger( AsyncRegistration asyncRegistration, String enrollmentId, Map<String, List<String>> headers, AsyncFetchStatus asyncFetchStatus)125 public Optional<Trigger> parseTrigger( 126 AsyncRegistration asyncRegistration, 127 String enrollmentId, 128 Map<String, List<String>> headers, 129 AsyncFetchStatus asyncFetchStatus) { 130 boolean arDebugPermission = asyncRegistration.getDebugKeyAllowed(); 131 LoggerFactory.getMeasurementLogger() 132 .d("Trigger ArDebug permission enabled %b", arDebugPermission); 133 String triggerId = UUID.randomUUID().toString(); 134 Trigger.Builder builder = new Trigger.Builder(); 135 builder.setId(triggerId); 136 builder.setEnrollmentId(enrollmentId); 137 builder.setAttributionDestination( 138 getAttributionDestination( 139 asyncRegistration.getTopOrigin(), asyncRegistration.getType())); 140 builder.setRegistrant(asyncRegistration.getRegistrant()); 141 builder.setAdIdPermission(asyncRegistration.hasAdIdPermission()); 142 builder.setArDebugPermission(arDebugPermission); 143 builder.setDestinationType( 144 asyncRegistration.isWebRequest() ? EventSurfaceType.WEB : EventSurfaceType.APP); 145 builder.setTriggerTime(asyncRegistration.getRequestTime()); 146 Optional<Uri> registrationUriOrigin = 147 WebAddresses.originAndScheme(asyncRegistration.getRegistrationUri()); 148 LoggerFactory.getMeasurementLogger() 149 .d( 150 "AsyncTriggerFetcher: Parsing Trigger. Enrollment ID: %s, Trigger ID: %s", 151 enrollmentId, triggerId); 152 if (!registrationUriOrigin.isPresent()) { 153 LoggerFactory.getMeasurementLogger() 154 .d( 155 "AsyncTriggerFetcher: Invalid registration uri. Host: %s, Enrollment" 156 + " ID: %s, Trigger ID: %s", 157 asyncRegistration.getRegistrationUri().getHost(), 158 enrollmentId, 159 triggerId); 160 return Optional.empty(); 161 } 162 builder.setRegistrationOrigin(registrationUriOrigin.get()); 163 builder.setPlatformAdId(asyncRegistration.getPlatformAdId()); 164 165 boolean isHeaderErrorDebugReportEnabled = 166 FetcherUtil.isHeaderErrorDebugReportEnabled( 167 headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_INFO), 168 mFlags); 169 String registrationHeaderStr = null; 170 try { 171 List<String> field = 172 headers.get( 173 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 174 175 // Check the trigger registration header size. Only one header is accepted. 176 if (field == null || field.size() != 1) { 177 registrationHeaderStr = field == null ? null : field.toString(); 178 asyncFetchStatus.setEntityStatus(EntityStatus.HEADER_ERROR); 179 LoggerFactory.getMeasurementLogger() 180 .d( 181 "AsyncTriggerFetcher: null, empty, or multiple %s headers. " 182 + "Enrollment ID: %s, Trigger ID: %s", 183 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 184 enrollmentId, 185 triggerId); 186 FetcherUtil.sendHeaderErrorDebugReport( 187 isHeaderErrorDebugReportEnabled, 188 mDebugReportApi, 189 mDatastoreManager, 190 asyncRegistration.getTopOrigin(), 191 registrationUriOrigin.get(), 192 asyncRegistration.getRegistrant(), 193 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 194 enrollmentId, 195 registrationHeaderStr); 196 return Optional.empty(); 197 } 198 199 // Validate trigger header parameters. 200 registrationHeaderStr = field.get(0); 201 boolean isValid = 202 parseValidateTrigger( 203 registrationHeaderStr, 204 asyncRegistration, 205 builder, 206 enrollmentId, 207 triggerId, 208 asyncFetchStatus); 209 if (!isValid) { 210 asyncFetchStatus.setEntityStatus(EntityStatus.VALIDATION_ERROR); 211 LoggerFactory.getMeasurementLogger() 212 .d( 213 "AsyncTriggerFetcher: Invalid trigger params in %s header. " 214 + "Enrollment ID: %s, Trigger ID: %s", 215 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 216 enrollmentId, 217 triggerId); 218 FetcherUtil.sendHeaderErrorDebugReport( 219 isHeaderErrorDebugReportEnabled, 220 mDebugReportApi, 221 mDatastoreManager, 222 asyncRegistration.getTopOrigin(), 223 registrationUriOrigin.get(), 224 asyncRegistration.getRegistrant(), 225 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 226 enrollmentId, 227 registrationHeaderStr); 228 return Optional.empty(); 229 } 230 231 // Set success status and return parsed trigger if no error. 232 asyncFetchStatus.setEntityStatus(EntityStatus.SUCCESS); 233 return Optional.of(builder.build()); 234 } catch (JSONException e) { 235 asyncFetchStatus.setEntityStatus(EntityStatus.PARSING_ERROR); 236 LoggerFactory.getMeasurementLogger() 237 .d( 238 e, 239 "AsyncTriggerFetcher: %s header JSON Parsing Exception. " 240 + "Enrollment ID: %s, Trigger ID: %s", 241 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 242 enrollmentId, 243 triggerId); 244 FetcherUtil.sendHeaderErrorDebugReport( 245 isHeaderErrorDebugReportEnabled, 246 mDebugReportApi, 247 mDatastoreManager, 248 asyncRegistration.getTopOrigin(), 249 registrationUriOrigin.get(), 250 asyncRegistration.getRegistrant(), 251 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 252 enrollmentId, 253 registrationHeaderStr); 254 return Optional.empty(); 255 } catch (IllegalArgumentException e) { 256 asyncFetchStatus.setEntityStatus(EntityStatus.VALIDATION_ERROR); 257 LoggerFactory.getMeasurementLogger() 258 .d( 259 e, 260 "AsyncTriggerFetcher: IllegalArgumentException. Enrollment ID: %s," 261 + " Trigger ID: %s", 262 enrollmentId, 263 triggerId); 264 FetcherUtil.sendHeaderErrorDebugReport( 265 isHeaderErrorDebugReportEnabled, 266 mDebugReportApi, 267 mDatastoreManager, 268 asyncRegistration.getTopOrigin(), 269 registrationUriOrigin.get(), 270 asyncRegistration.getRegistrant(), 271 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 272 enrollmentId, 273 registrationHeaderStr); 274 return Optional.empty(); 275 } 276 } 277 getValidTriggerContextId(JSONObject json)278 private Optional<String> getValidTriggerContextId(JSONObject json) throws JSONException { 279 Object triggerContextIdObj = json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID); 280 if (!(triggerContextIdObj instanceof String)) { 281 LoggerFactory.getMeasurementLogger() 282 .d( 283 "%s: %s, is not a String", 284 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 285 json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID).toString()); 286 return Optional.empty(); 287 } 288 289 String contextId = triggerContextIdObj.toString(); 290 if (contextId.length() > mFlags.getMeasurementMaxLengthOfTriggerContextId()) { 291 LoggerFactory.getMeasurementLogger() 292 .d( 293 "Length of %s: \"%s\", exceeds max length of %d", 294 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 295 contextId, 296 mFlags.getMeasurementMaxLengthOfTriggerContextId()); 297 return Optional.empty(); 298 } 299 300 return Optional.of(contextId); 301 } 302 getSourceRegistrationTimeConfig(JSONObject json)303 private Trigger.SourceRegistrationTimeConfig getSourceRegistrationTimeConfig(JSONObject json) { 304 Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig = 305 Trigger.SourceRegistrationTimeConfig.EXCLUDE; 306 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME)) { 307 String sourceRegistrationTimeConfigString = 308 json.optString(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME) 309 .toUpperCase(Locale.ENGLISH); 310 sourceRegistrationTimeConfig = 311 Trigger.SourceRegistrationTimeConfig.valueOf( 312 sourceRegistrationTimeConfigString); 313 } 314 315 316 return sourceRegistrationTimeConfig; 317 } 318 isAllowlisted(String allowlist, String origin)319 private boolean isAllowlisted(String allowlist, String origin) { 320 if (AllowLists.doesAllowListAllowAll(allowlist)) { 321 return true; 322 } 323 Set<String> elements = new HashSet<>(AllowLists.splitAllowList(allowlist)); 324 return elements.contains(origin); 325 } 326 327 /** Provided a testing hook. */ 328 @NonNull openUrl(@onNull URL url)329 public URLConnection openUrl(@NonNull URL url) throws IOException { 330 return mNetworkConnection.setup(url); 331 } 332 isXnaAllowedForTriggerRegistrant( Uri registrant, AsyncRegistration.RegistrationType registrationType)333 private boolean isXnaAllowedForTriggerRegistrant( 334 Uri registrant, AsyncRegistration.RegistrationType registrationType) { 335 // If the trigger is registered from web context, only allow-listed apps should be able to 336 // parse attribution config. 337 return !AsyncRegistration.RegistrationType.WEB_TRIGGER.equals(registrationType) 338 || AllowLists.isPackageAllowListed( 339 mFlags.getWebContextClientAppAllowList(), registrant.getAuthority()); 340 } 341 342 /** 343 * Fetch a trigger type registration. 344 * 345 * @param asyncRegistration a {@link AsyncRegistration}, a request the record. 346 * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status. 347 * @param asyncRedirects a {@link AsyncRedirects}, stores redirections. 348 */ fetchTrigger( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirects asyncRedirects)349 public Optional<Trigger> fetchTrigger( 350 AsyncRegistration asyncRegistration, 351 AsyncFetchStatus asyncFetchStatus, 352 AsyncRedirects asyncRedirects) { 353 HttpURLConnection urlConnection = null; 354 Map<String, List<String>> headers; 355 if (!asyncRegistration.getRegistrationUri().getScheme().equalsIgnoreCase("https")) { 356 LoggerFactory.getMeasurementLogger() 357 .d( 358 "AsyncTriggerFetcher: Invalid scheme for registrationUri - %s", 359 asyncRegistration.getRegistrationUri().getScheme()); 360 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL); 361 return Optional.empty(); 362 } 363 // TODO(b/276825561): Fix code duplication between fetchSource & fetchTrigger request flow 364 Optional<String> enrollmentId = Optional.empty(); 365 try { 366 urlConnection = 367 (HttpURLConnection) 368 openUrl(new URL(asyncRegistration.getRegistrationUri().toString())); 369 urlConnection.setRequestMethod("POST"); 370 urlConnection.setInstanceFollowRedirects(false); 371 headers = urlConnection.getHeaderFields(); 372 enrollmentId = getEnrollmentId(asyncRegistration); 373 374 // get ODP header from headers map and forward ODP header 375 long odpHeaderSize = 0; 376 Optional<Map<String, List<String>>> odpHeader = getOdpTriggerHeader(headers); 377 if (odpHeader.isPresent()) { 378 mOdpWrapper.registerOdpTrigger( 379 asyncRegistration, odpHeader.get(), enrollmentId.isPresent()); 380 odpHeaderSize = FetcherUtil.calculateHeadersCharactersLength(odpHeader.get()); 381 } 382 383 long headerSize = FetcherUtil.calculateHeadersCharactersLength(headers) - odpHeaderSize; 384 if (mFlags.getMeasurementEnableUpdateTriggerHeaderLimit() 385 && headerSize > mFlags.getMaxTriggerRegistrationHeaderSizeBytes()) { 386 LoggerFactory.getMeasurementLogger() 387 .d( 388 "AsyncTriggerFetcher: Trigger registration header size exceeds" 389 + " limit bytes %s. Host: %s", 390 mFlags.getMaxTriggerRegistrationHeaderSizeBytes(), 391 urlConnection.getURL().getHost()); 392 asyncFetchStatus.setResponseStatus( 393 AsyncFetchStatus.ResponseStatus.HEADER_SIZE_LIMIT_EXCEEDED); 394 return Optional.empty(); 395 } 396 asyncFetchStatus.setResponseSize(headerSize); 397 int responseCode = urlConnection.getResponseCode(); 398 LoggerFactory.getMeasurementLogger() 399 .d( 400 "AsyncTriggerFetcher - Response code: %s, Method: %s, Host: %s", 401 responseCode, 402 urlConnection.getRequestMethod(), 403 urlConnection.getURL().getHost()); 404 if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) { 405 asyncFetchStatus.setResponseStatus( 406 AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE); 407 return Optional.empty(); 408 } 409 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.SUCCESS); 410 } catch (MalformedURLException e) { 411 LoggerFactory.getMeasurementLogger() 412 .e(e, "AsyncTriggerFetcher: Malformed registration target URL"); 413 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL); 414 return Optional.empty(); 415 } catch (IOException e) { 416 LoggerFactory.getMeasurementLogger() 417 .e(e, "AsyncTriggerFetcher: Failed to get registration response"); 418 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR); 419 return Optional.empty(); 420 } finally { 421 if (urlConnection != null) { 422 urlConnection.disconnect(); 423 } 424 } 425 426 asyncRedirects.configure(headers, asyncRegistration); 427 428 if (!isTriggerHeaderPresent(headers)) { 429 asyncFetchStatus.setEntityStatus(EntityStatus.HEADER_MISSING); 430 asyncFetchStatus.setRedirectOnlyStatus(true); 431 LoggerFactory.getMeasurementLogger() 432 .d( 433 "AsyncTriggerFetcher: %s header not found. Host: %s", 434 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 435 urlConnection.getURL().getHost()); 436 return Optional.empty(); 437 } 438 439 if (enrollmentId.isEmpty()) { 440 LoggerFactory.getMeasurementLogger() 441 .d( 442 "AsyncTriggerFetcher: Enrollment ID could not be verified. Host: %s", 443 urlConnection.getURL().getHost()); 444 asyncFetchStatus.setEntityStatus(EntityStatus.INVALID_ENROLLMENT); 445 ErrorLogUtil.e( 446 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID, 447 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 448 return Optional.empty(); 449 } 450 return parseTrigger(asyncRegistration, enrollmentId.get(), headers, asyncFetchStatus); 451 } 452 453 /** Return instance of IOdpDelegationWrapper. */ getOdpWrapper()454 public IOdpDelegationWrapper getOdpWrapper() { 455 return mOdpWrapper; 456 } 457 parseValidateTrigger( String registrationHeaderStr, AsyncRegistration asyncRegistration, Trigger.Builder builder, String enrollmentId, String triggerId, AsyncFetchStatus asyncFetchStatus)458 private boolean parseValidateTrigger( 459 String registrationHeaderStr, 460 AsyncRegistration asyncRegistration, 461 Trigger.Builder builder, 462 String enrollmentId, 463 String triggerId, 464 AsyncFetchStatus asyncFetchStatus) 465 throws JSONException { 466 String eventTriggerData = new JSONArray().toString(); 467 JSONObject json = new JSONObject(registrationHeaderStr); 468 if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) { 469 Optional<String> validEventTriggerData = 470 getValidEventTriggerData( 471 json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA)); 472 if (!validEventTriggerData.isPresent()) { 473 logInvalidTriggerField( 474 TriggerHeaderContract.EVENT_TRIGGER_DATA, enrollmentId, triggerId); 475 return false; 476 } 477 eventTriggerData = validEventTriggerData.get(); 478 } 479 builder.setEventTriggers(eventTriggerData); 480 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) { 481 Optional<String> validAggregateTriggerData = 482 getValidAggregateTriggerData( 483 json.getJSONArray(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)); 484 if (!validAggregateTriggerData.isPresent()) { 485 logInvalidTriggerField( 486 TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA, enrollmentId, triggerId); 487 return false; 488 } 489 builder.setAggregateTriggerData(validAggregateTriggerData.get()); 490 } 491 Integer filteringIdMaxBytes = null; 492 if (mFlags.getMeasurementEnableFlexibleContributionFiltering()) { 493 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES)) { 494 Optional<Integer> maybeAggregatableFilteringIdMaxBytes = 495 getValidAggregatableFilteringIdMaxBytes(json); 496 if (!maybeAggregatableFilteringIdMaxBytes.isPresent()) { 497 logInvalidTriggerField( 498 TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES, 499 enrollmentId, 500 triggerId); 501 return false; 502 } 503 filteringIdMaxBytes = maybeAggregatableFilteringIdMaxBytes.get(); 504 builder.setAggregatableFilteringIdMaxBytes(filteringIdMaxBytes); 505 } else { 506 filteringIdMaxBytes = mFlags.getMeasurementDefaultFilteringIdMaxBytes(); 507 } 508 } 509 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_VALUES)) { 510 Object maybeValidAggregatableValues = 511 json.get(TriggerHeaderContract.AGGREGATABLE_VALUES); 512 boolean invalidAggregatableValuesType = 513 !(maybeValidAggregatableValues instanceof JSONObject) 514 && !(maybeValidAggregatableValues instanceof JSONArray); 515 boolean rejectJsonArray = 516 (maybeValidAggregatableValues instanceof JSONArray) 517 && !(mFlags.getMeasurementEnableAggregateValueFilters()); 518 if (invalidAggregatableValuesType || rejectJsonArray) { 519 LoggerFactory.getMeasurementLogger() 520 .d( 521 "AsyncTriggerFetcher: Invalid aggregate value type in" 522 + " %s. Enrollment ID: %s, Trigger ID: %s", 523 TriggerHeaderContract.AGGREGATABLE_VALUES, enrollmentId, triggerId); 524 return false; 525 } 526 if (maybeValidAggregatableValues instanceof JSONObject) { 527 if (!isValidAggregateValues( 528 (JSONObject) maybeValidAggregatableValues, 529 filteringIdMaxBytes, 530 asyncFetchStatus)) { 531 LoggerFactory.getMeasurementLogger() 532 .d( 533 "AsyncTriggerFetcher: Invalid values found in" 534 + " %s. Enrollment ID: %s, Trigger ID: %s", 535 TriggerHeaderContract.AGGREGATABLE_VALUES, 536 enrollmentId, 537 triggerId); 538 return false; 539 } 540 } else { 541 if (!isValidAggregatableValuesJsonArray( 542 (JSONArray) maybeValidAggregatableValues, 543 filteringIdMaxBytes, 544 asyncFetchStatus)) { 545 LoggerFactory.getMeasurementLogger() 546 .d( 547 "AsyncTriggerFetcher: Invalid %s array. Enrollment ID: %s," 548 + " Trigger ID: %s", 549 TriggerHeaderContract.AGGREGATABLE_VALUES, 550 enrollmentId, 551 triggerId); 552 return false; 553 } 554 if (mFlags.getMeasurementEnableAggregateValueFilters()) { 555 asyncFetchStatus.setIsTriggerAggregatableValueFiltersConfigured(true); 556 } 557 } 558 builder.setAggregateValuesString( 559 json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES)); 560 } 561 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)) { 562 Optional<String> validAggregateDeduplicationKeysString = 563 getValidAggregateDuplicationKeysString( 564 json.getJSONArray( 565 TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)); 566 if (!validAggregateDeduplicationKeysString.isPresent()) { 567 logInvalidTriggerField( 568 TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS, 569 enrollmentId, 570 triggerId); 571 return false; 572 } 573 builder.setAggregateDeduplicationKeys(validAggregateDeduplicationKeysString.get()); 574 } 575 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 576 if (!json.isNull(FilterContract.FILTERS)) { 577 JSONArray filters = Filter.maybeWrapFilters(json, FilterContract.FILTERS); 578 if (!FetcherUtil.areValidAttributionFilters( 579 filters, mFlags, true, shouldCheckFilterSize)) { 580 logInvalidTriggerField(FilterContract.FILTERS, enrollmentId, triggerId); 581 return false; 582 } 583 builder.setFilters(filters.toString()); 584 } 585 if (!json.isNull(FilterContract.NOT_FILTERS)) { 586 JSONArray notFilters = Filter.maybeWrapFilters(json, FilterContract.NOT_FILTERS); 587 if (!FetcherUtil.areValidAttributionFilters( 588 notFilters, mFlags, true, shouldCheckFilterSize)) { 589 logInvalidTriggerField(FilterContract.NOT_FILTERS, enrollmentId, triggerId); 590 return false; 591 } 592 builder.setNotFilters(notFilters.toString()); 593 } 594 if (!json.isNull(TriggerHeaderContract.DEBUG_REPORTING)) { 595 builder.setIsDebugReporting(json.optBoolean(TriggerHeaderContract.DEBUG_REPORTING)); 596 } 597 if (!json.isNull(TriggerHeaderContract.DEBUG_KEY)) { 598 Optional<UnsignedLong> maybeDebugKey = 599 FetcherUtil.extractUnsignedLong(json, TriggerHeaderContract.DEBUG_KEY); 600 if (maybeDebugKey.isPresent()) { 601 builder.setDebugKey(maybeDebugKey.get()); 602 } 603 } 604 if (mFlags.getMeasurementEnableXNA() 605 && !json.isNull(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)) { 606 if (!isValidXNetworkKeyMapping( 607 json.getJSONObject(TriggerHeaderContract.X_NETWORK_KEY_MAPPING))) { 608 LoggerFactory.getMeasurementLogger() 609 .d( 610 "AsyncTriggerFetcher: Invalid %s, continuing to process trigger." 611 + " Enrollment ID: %s, Trigger ID: %s", 612 TriggerHeaderContract.X_NETWORK_KEY_MAPPING, 613 enrollmentId, 614 triggerId); 615 } else { 616 builder.setAdtechBitMapping( 617 json.getString(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)); 618 } 619 } 620 if (mFlags.getMeasurementEnableXNA() 621 && isXnaAllowedForTriggerRegistrant( 622 asyncRegistration.getRegistrant(), asyncRegistration.getType()) 623 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_CONFIG)) { 624 String attributionConfigsString = 625 extractValidAttributionConfigs( 626 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_CONFIG)); 627 builder.setAttributionConfig(attributionConfigsString); 628 } 629 630 if (mFlags.getMeasurementAggregationCoordinatorOriginEnabled() 631 && !json.isNull(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN)) { 632 String origin = json.getString(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN); 633 String allowlist = mFlags.getMeasurementAggregationCoordinatorOriginList(); 634 if (origin.isEmpty() || !isAllowlisted(allowlist, origin)) { 635 logInvalidTriggerField( 636 TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN, 637 enrollmentId, 638 triggerId); 639 return false; 640 } 641 builder.setAggregationCoordinatorOrigin(Uri.parse(origin)); 642 } 643 644 String enrollmentBlockList = 645 mFlags.getMeasurementPlatformDebugAdIdMatchingEnrollmentBlocklist(); 646 Set<String> blockedEnrollmentsString = 647 new HashSet<>(AllowLists.splitAllowList(enrollmentBlockList)); 648 if (!AllowLists.doesAllowListAllowAll(enrollmentBlockList) 649 && !blockedEnrollmentsString.contains(enrollmentId) 650 && !json.isNull(TriggerHeaderContract.DEBUG_AD_ID)) { 651 builder.setDebugAdId(json.optString(TriggerHeaderContract.DEBUG_AD_ID)); 652 } 653 654 Set<String> allowedEnrollmentsString = 655 new HashSet<>( 656 AllowLists.splitAllowList( 657 mFlags.getMeasurementDebugJoinKeyEnrollmentAllowlist())); 658 if (allowedEnrollmentsString.contains(enrollmentId) 659 && !json.isNull(TriggerHeaderContract.DEBUG_JOIN_KEY)) { 660 builder.setDebugJoinKey(json.optString(TriggerHeaderContract.DEBUG_JOIN_KEY)); 661 } 662 663 Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig = 664 getSourceRegistrationTimeConfig(json); 665 666 if (filteringIdMaxBytes != null 667 && filteringIdMaxBytes != mFlags.getMeasurementDefaultFilteringIdMaxBytes() 668 && Trigger.SourceRegistrationTimeConfig.INCLUDE.equals( 669 sourceRegistrationTimeConfig)) { 670 LoggerFactory.getMeasurementLogger() 671 .d( 672 "AsyncTriggerFetcher: Non-default %s cannot be provided when %s" 673 + " is %s. Enrollment ID: %s, Trigger ID: %s", 674 TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES, 675 TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME, 676 Trigger.SourceRegistrationTimeConfig.INCLUDE.name(), 677 enrollmentId, 678 triggerId); 679 return false; 680 } 681 682 builder.setAggregatableSourceRegistrationTimeConfig(sourceRegistrationTimeConfig); 683 684 if (!json.isNull(TriggerHeaderContract.TRIGGER_CONTEXT_ID)) { 685 if (Trigger.SourceRegistrationTimeConfig.INCLUDE.equals(sourceRegistrationTimeConfig)) { 686 LoggerFactory.getMeasurementLogger() 687 .d( 688 "AsyncTriggerFetcher: %s cannot be provided when %s" 689 + " is %s. Enrollment ID: %s, Trigger ID: %s", 690 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 691 TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME, 692 Trigger.SourceRegistrationTimeConfig.INCLUDE.name(), 693 enrollmentId, 694 triggerId); 695 return false; 696 } 697 698 Optional<String> contextIdOpt = getValidTriggerContextId(json); 699 if (contextIdOpt.isEmpty()) { 700 logInvalidTriggerField( 701 TriggerHeaderContract.TRIGGER_CONTEXT_ID, enrollmentId, triggerId); 702 return false; 703 } 704 705 asyncFetchStatus.setIsTriggerContextIdConfigured(true); 706 builder.setTriggerContextId(contextIdOpt.get()); 707 } 708 709 if (mFlags.getMeasurementEnableAttributionScope() 710 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_SCOPES)) { 711 Optional<List<String>> attributionScopes = 712 FetcherUtil.extractStringArray( 713 json, 714 TriggerHeaderContract.ATTRIBUTION_SCOPES, 715 Integer.MAX_VALUE, 716 Integer.MAX_VALUE); 717 if (attributionScopes.isEmpty()) { 718 logInvalidTriggerField( 719 TriggerHeaderContract.ATTRIBUTION_SCOPES, enrollmentId, triggerId); 720 return false; 721 } 722 builder.setAttributionScopesString( 723 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_SCOPES).toString()); 724 } 725 726 if (mFlags.getMeasurementEnableAggregateDebugReporting() 727 && !json.isNull(TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING)) { 728 Optional<String> validAggregateDebugReporting = 729 FetcherUtil.getValidAggregateDebugReportingWithoutBudget( 730 json.getJSONObject(TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING), 731 mFlags); 732 if (validAggregateDebugReporting.isPresent()) { 733 builder.setAggregateDebugReportingString(validAggregateDebugReporting.get()); 734 } else { 735 LoggerFactory.getMeasurementLogger() 736 .d( 737 "AsyncTriggerFetcher: Invalid %s, continuing to process trigger. " 738 + "Enrollment ID: %s, Trigger ID: %s", 739 TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING, 740 enrollmentId, 741 triggerId); 742 } 743 } 744 745 if (mFlags.getMeasurementEnableAggregatableNamedBudgets() 746 && !json.isNull(NamedBudgetContract.NAMED_BUDGETS)) { 747 Object namedBudgetsObj = json.get(NamedBudgetContract.NAMED_BUDGETS); 748 if (!(namedBudgetsObj instanceof JSONArray)) { 749 LoggerFactory.getMeasurementLogger() 750 .d( 751 "AsyncTriggerFetcher: %s is not an array." 752 + " Enrollment ID: %s, Trigger ID: %s", 753 NamedBudgetContract.NAMED_BUDGETS, enrollmentId, triggerId); 754 return false; 755 } 756 JSONArray namedBudgetsArray = (JSONArray) namedBudgetsObj; 757 Optional<String> maybeNamedBudgets = parseNamedBudgets(namedBudgetsArray); 758 if (maybeNamedBudgets.isEmpty()) { 759 logInvalidTriggerField(NamedBudgetContract.NAMED_BUDGETS, enrollmentId, triggerId); 760 return false; 761 } 762 builder.setNamedBudgetsString(maybeNamedBudgets.get()); 763 } 764 765 return true; 766 } 767 isTriggerHeaderPresent(Map<String, List<String>> headers)768 private boolean isTriggerHeaderPresent(Map<String, List<String>> headers) { 769 return headers.containsKey( 770 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 771 } 772 getOdpTriggerHeader( Map<String, List<String>> headers)773 private Optional<Map<String, List<String>>> getOdpTriggerHeader( 774 Map<String, List<String>> headers) { 775 return headers.containsKey(OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER) 776 ? Optional.of(Map.of( 777 OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER, 778 headers.get( 779 OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER))) 780 : Optional.empty(); 781 } 782 getEnrollmentId(AsyncRegistration asyncRegistration)783 private Optional<String> getEnrollmentId(AsyncRegistration asyncRegistration) { 784 return mFlags.isDisableMeasurementEnrollmentCheck() 785 ? WebAddresses.topPrivateDomainAndScheme(asyncRegistration.getRegistrationUri()) 786 .map(Uri::toString) 787 : Enrollment.getValidEnrollmentId( 788 asyncRegistration.getRegistrationUri(), 789 asyncRegistration.getRegistrant().getAuthority(), 790 mEnrollmentDao, 791 mContext, 792 mFlags); 793 } 794 parseNamedBudgets(JSONArray namedBudgetsArray)795 private Optional<String> parseNamedBudgets(JSONArray namedBudgetsArray) throws JSONException { 796 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 797 JSONArray validNamedBudgets = new JSONArray(); 798 799 for (int i = 0; i < namedBudgetsArray.length(); i++) { 800 JSONObject namedBudget = new JSONObject(); 801 Object maybeBudgetObj = namedBudgetsArray.get(i); 802 803 if (!(maybeBudgetObj instanceof JSONObject)) { 804 LoggerFactory.getMeasurementLogger() 805 .d("parseNamedBudgets: named budget " + i + " is not a JSON object."); 806 return Optional.empty(); 807 } 808 JSONObject budgetObj = (JSONObject) maybeBudgetObj; 809 810 if (!budgetObj.isNull(NamedBudgetContract.NAME)) { 811 if (!(budgetObj.get(NamedBudgetContract.NAME) instanceof String)) { 812 LoggerFactory.getMeasurementLogger() 813 .d( 814 "parseNamedBudgets: Value for named budget " 815 + i 816 + " \"name\" is not a string."); 817 return Optional.empty(); 818 } 819 namedBudget.put( 820 NamedBudgetContract.NAME, budgetObj.getString(NamedBudgetContract.NAME)); 821 } 822 if (!budgetObj.isNull(FilterContract.FILTERS)) { 823 JSONArray filters = Filter.maybeWrapFilters(budgetObj, FilterContract.FILTERS); 824 if (!FetcherUtil.areValidAttributionFilters( 825 filters, 826 mFlags, 827 /* canIncludeLookbackWindow= */ true, 828 shouldCheckFilterSize)) { 829 LoggerFactory.getMeasurementLogger() 830 .d( 831 "parseNamedBudgets: Named budget " 832 + i 833 + " contains invalid filters."); 834 return Optional.empty(); 835 } 836 namedBudget.put(FilterContract.FILTERS, filters); 837 } 838 if (!budgetObj.isNull(FilterContract.NOT_FILTERS)) { 839 JSONArray notFilters = 840 Filter.maybeWrapFilters(budgetObj, FilterContract.NOT_FILTERS); 841 if (!FetcherUtil.areValidAttributionFilters( 842 notFilters, 843 mFlags, 844 /* canIncludeLookbackWindow= */ true, 845 shouldCheckFilterSize)) { 846 LoggerFactory.getMeasurementLogger() 847 .d( 848 "parseNamedBudgets: Named budget " 849 + i 850 + " contains invalid not_filters."); 851 return Optional.empty(); 852 } 853 namedBudget.put(FilterContract.NOT_FILTERS, notFilters); 854 } 855 validNamedBudgets.put(namedBudget); 856 } 857 return Optional.of(validNamedBudgets.toString()); 858 } 859 getValidEventTriggerData(JSONArray eventTriggerDataArr)860 private Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) { 861 JSONArray validEventTriggerData = new JSONArray(); 862 for (int i = 0; i < eventTriggerDataArr.length(); i++) { 863 JSONObject validEventTriggerDatum = new JSONObject(); 864 try { 865 JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i); 866 UnsignedLong triggerData = new UnsignedLong(0L); 867 if (!eventTriggerDatum.isNull("trigger_data")) { 868 Optional<UnsignedLong> maybeTriggerData = 869 FetcherUtil.extractUnsignedLong(eventTriggerDatum, "trigger_data"); 870 if (!maybeTriggerData.isPresent()) { 871 return Optional.empty(); 872 } 873 triggerData = maybeTriggerData.get(); 874 } 875 validEventTriggerDatum.put("trigger_data", triggerData); 876 if (!eventTriggerDatum.isNull("priority")) { 877 Optional<Long> maybePriority = 878 FetcherUtil.extractLongString(eventTriggerDatum, "priority"); 879 if (!maybePriority.isPresent()) { 880 return Optional.empty(); 881 } 882 validEventTriggerDatum.put("priority", String.valueOf(maybePriority.get())); 883 } 884 if (!eventTriggerDatum.isNull("value")) { 885 Optional<Long> maybeValue = 886 FetcherUtil.extractLong(eventTriggerDatum, "value"); 887 if (!maybeValue.isPresent()) { 888 return Optional.empty(); 889 } 890 long value = maybeValue.get(); 891 if (value < 1L || value > TriggerSpecs.MAX_BUCKET_THRESHOLD) { 892 return Optional.empty(); 893 } 894 validEventTriggerDatum.put("value", value); 895 } 896 if (!eventTriggerDatum.isNull("deduplication_key")) { 897 Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong( 898 eventTriggerDatum, "deduplication_key"); 899 if (!maybeDedupKey.isPresent()) { 900 return Optional.empty(); 901 } 902 validEventTriggerDatum.put("deduplication_key", maybeDedupKey.get()); 903 } 904 boolean shouldCheckFilterSize = 905 !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 906 if (!eventTriggerDatum.isNull(FilterContract.FILTERS)) { 907 JSONArray filters = 908 Filter.maybeWrapFilters(eventTriggerDatum, FilterContract.FILTERS); 909 if (!FetcherUtil.areValidAttributionFilters( 910 filters, 911 mFlags, 912 /* canIncludeLookbackWindow= */ true, 913 shouldCheckFilterSize)) { 914 LoggerFactory.getMeasurementLogger() 915 .d("getValidEventTriggerData: filters are invalid."); 916 return Optional.empty(); 917 } 918 validEventTriggerDatum.put(FilterContract.FILTERS, filters); 919 } 920 if (!eventTriggerDatum.isNull(FilterContract.NOT_FILTERS)) { 921 JSONArray notFilters = 922 Filter.maybeWrapFilters(eventTriggerDatum, FilterContract.NOT_FILTERS); 923 if (!FetcherUtil.areValidAttributionFilters( 924 notFilters, 925 mFlags, 926 /* canIncludeLookbackWindow= */ true, 927 shouldCheckFilterSize)) { 928 LoggerFactory.getMeasurementLogger() 929 .d("getValidEventTriggerData: not-filters are invalid."); 930 return Optional.empty(); 931 } 932 validEventTriggerDatum.put(FilterContract.NOT_FILTERS, notFilters); 933 } 934 validEventTriggerData.put(validEventTriggerDatum); 935 } catch (JSONException e) { 936 LoggerFactory.getMeasurementLogger() 937 .d(e, "AsyncTriggerFetcher: JSONException parsing event trigger datum."); 938 return Optional.empty(); 939 } catch (NumberFormatException e) { 940 LoggerFactory.getMeasurementLogger() 941 .d( 942 e, 943 "AsyncTriggerFetcher: NumberFormatException parsing event trigger " 944 + "datum."); 945 return Optional.empty(); 946 } 947 } 948 return Optional.of(validEventTriggerData.toString()); 949 } 950 getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)951 private Optional<String> getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr) 952 throws JSONException { 953 JSONArray validAggregateTriggerData = new JSONArray(); 954 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 955 for (int i = 0; i < aggregateTriggerDataArr.length(); i++) { 956 JSONObject aggregateTriggerData = aggregateTriggerDataArr.getJSONObject(i); 957 String keyPiece = aggregateTriggerData.optString("key_piece"); 958 if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece, mFlags)) { 959 LoggerFactory.getMeasurementLogger() 960 .d("Aggregate trigger data key-piece is invalid. %s", keyPiece); 961 return Optional.empty(); 962 } 963 JSONArray sourceKeys; 964 if (aggregateTriggerData.isNull("source_keys")) { 965 sourceKeys = new JSONArray(); 966 aggregateTriggerData.put("source_keys", sourceKeys); 967 } else { 968 // Registration will be rejected if source-keys is not a list 969 sourceKeys = aggregateTriggerData.getJSONArray("source_keys"); 970 } 971 if (shouldCheckFilterSize 972 && sourceKeys.length() 973 > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) { 974 LoggerFactory.getMeasurementLogger() 975 .d( 976 "Aggregate trigger data source-keys list has more entries " 977 + "than permitted."); 978 return Optional.empty(); 979 } 980 for (int j = 0; j < sourceKeys.length(); j++) { 981 Object sourceKey = sourceKeys.get(j); 982 if (!(sourceKey instanceof String) 983 || !FetcherUtil.isValidAggregateKeyId((String) sourceKey)) { 984 LoggerFactory.getMeasurementLogger() 985 .d("Aggregate trigger data source-key is invalid. %s", sourceKey); 986 return Optional.empty(); 987 } 988 } 989 if (!aggregateTriggerData.isNull(FilterContract.FILTERS)) { 990 JSONArray filters = 991 Filter.maybeWrapFilters(aggregateTriggerData, FilterContract.FILTERS); 992 if (!FetcherUtil.areValidAttributionFilters( 993 filters, 994 mFlags, 995 /* canIncludeLookbackWindow= */ true, 996 shouldCheckFilterSize)) { 997 LoggerFactory.getMeasurementLogger() 998 .d("Aggregate trigger data filters are invalid."); 999 return Optional.empty(); 1000 } 1001 aggregateTriggerData.put(FilterContract.FILTERS, filters); 1002 } 1003 if (!aggregateTriggerData.isNull(FilterContract.NOT_FILTERS)) { 1004 JSONArray notFilters = 1005 Filter.maybeWrapFilters(aggregateTriggerData, FilterContract.NOT_FILTERS); 1006 if (!FetcherUtil.areValidAttributionFilters( 1007 notFilters, 1008 mFlags, 1009 /* canIncludeLookbackWindow= */ true, 1010 shouldCheckFilterSize)) { 1011 LoggerFactory.getMeasurementLogger() 1012 .d("Aggregate trigger data not-filters are invalid."); 1013 return Optional.empty(); 1014 } 1015 aggregateTriggerData.put(FilterContract.NOT_FILTERS, notFilters); 1016 } 1017 if (!aggregateTriggerData.isNull("x_network_data")) { 1018 JSONObject xNetworkDataJson = aggregateTriggerData.getJSONObject("x_network_data"); 1019 // This is in order to validate the JSON parsing does not throw exception 1020 new XNetworkData.Builder(xNetworkDataJson); 1021 } 1022 validAggregateTriggerData.put(aggregateTriggerData); 1023 } 1024 return Optional.of(validAggregateTriggerData.toString()); 1025 } 1026 1027 /** 1028 * Returns true if all values in aggregatable_values are valid. Default Case: 1029 * {"campaignCounts":1664} Flexible Contribution Filtering: {"campaignCounts": {"value": 1664, 1030 * "filtering_id: 123}} 1031 */ isValidAggregateValues( JSONObject aggregateValues, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1032 private boolean isValidAggregateValues( 1033 JSONObject aggregateValues, 1034 Integer filteringIdMaxBytes, 1035 AsyncFetchStatus asyncFetchStatus) 1036 throws JSONException { 1037 if (!mFlags.getMeasurementEnableUpdateTriggerHeaderLimit() 1038 && aggregateValues.length() 1039 > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) { 1040 LoggerFactory.getMeasurementLogger() 1041 .d( 1042 "Aggregate values have more keys than permitted. %s", 1043 aggregateValues.length()); 1044 return false; 1045 } 1046 Iterator<String> ids = aggregateValues.keys(); 1047 while (ids.hasNext()) { 1048 String id = ids.next(); 1049 if (!FetcherUtil.isValidAggregateKeyId(id)) { 1050 LoggerFactory.getMeasurementLogger() 1051 .d("Aggregate values key ID is invalid. %s", id); 1052 return false; 1053 } 1054 Object value = aggregateValues.get(id); 1055 if (value instanceof JSONObject) { 1056 if (!mFlags.getMeasurementEnableFlexibleContributionFiltering() 1057 || !isValidAggregateValueObj( 1058 (JSONObject) value, filteringIdMaxBytes, asyncFetchStatus)) { 1059 return false; 1060 } 1061 } else if (!isValidAggregatableValuesValue(value)) { 1062 return false; 1063 } 1064 } 1065 return true; 1066 } 1067 1068 /* Returns true if the aggregatable_values#value field is valid. */ isValidAggregatableValuesValue(Object maybeValue)1069 private boolean isValidAggregatableValuesValue(Object maybeValue) { 1070 Optional<BigDecimal> maybeValidValue = FetcherUtil.extractIntegralValue(maybeValue); 1071 if (maybeValidValue.isEmpty()) { 1072 return false; 1073 } 1074 BigDecimal validValue = maybeValidValue.get(); 1075 if (validValue.compareTo(BigDecimal.valueOf(1)) < 0 1076 || validValue.compareTo( 1077 BigDecimal.valueOf( 1078 mFlags.getMeasurementMaxSumOfAggregateValuesPerSource())) 1079 > 0) { 1080 return false; 1081 } 1082 return true; 1083 } 1084 1085 /** 1086 * Returns true if JSONArray aggregatable_values is valid. Aggregate Values Filtering: [{ 1087 * "values": {"campaignCounts": 32768}, "filters": {"category": ["filter_1"]}, 1088 * "not_filters":{"category": ["filter_2"]} }] Flexible Contribution Filtering: [{ 1089 * "values":{"campaignCounts": {"value": 32768, "filtering_id": 123}}, "filters": {"category": 1090 * ["filter_1"]}, "not_filters": {"category": ["filter_2"]} }] 1091 */ isValidAggregatableValuesJsonArray( JSONArray aggregatableValuesArr, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1092 private boolean isValidAggregatableValuesJsonArray( 1093 JSONArray aggregatableValuesArr, 1094 Integer filteringIdMaxBytes, 1095 AsyncFetchStatus asyncFetchStatus) 1096 throws JSONException { 1097 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 1098 for (int i = 0; i < aggregatableValuesArr.length(); i++) { 1099 JSONObject aggregatableValuesObj = aggregatableValuesArr.getJSONObject(i); 1100 // validate values 1101 if (aggregatableValuesObj.isNull(AggregatableValuesConfigContract.VALUES) 1102 || !isValidAggregateValues( 1103 aggregatableValuesObj.getJSONObject( 1104 AggregatableValuesConfigContract.VALUES), 1105 filteringIdMaxBytes, 1106 asyncFetchStatus)) { 1107 LoggerFactory.getMeasurementLogger() 1108 .d( 1109 "AGGREGATABLE_VALUES: %s is null or invalid.", 1110 AggregatableValuesConfigContract.VALUES); 1111 return false; 1112 } 1113 if (!aggregatableValuesObj.isNull(FilterContract.FILTERS)) { 1114 JSONArray filters = 1115 Filter.maybeWrapFilters(aggregatableValuesObj, FilterContract.FILTERS); 1116 if (!FetcherUtil.areValidAttributionFilters( 1117 filters, 1118 mFlags, 1119 /* canIncludeLookbackWindow= */ true, 1120 shouldCheckFilterSize)) { 1121 LoggerFactory.getMeasurementLogger() 1122 .d("AGGREGATABLE_VALUES: filters are invalid."); 1123 return false; 1124 } 1125 } 1126 // validate not_filters 1127 if (!aggregatableValuesObj.isNull(FilterContract.NOT_FILTERS)) { 1128 JSONArray not_filters = 1129 Filter.maybeWrapFilters(aggregatableValuesObj, FilterContract.NOT_FILTERS); 1130 if (!FetcherUtil.areValidAttributionFilters( 1131 not_filters, 1132 mFlags, 1133 /* canIncludeLookbackWindow= */ true, 1134 shouldCheckFilterSize)) { 1135 LoggerFactory.getMeasurementLogger() 1136 .d("AGGREGATABLE_VALUES: not_filters are invalid."); 1137 return false; 1138 } 1139 } 1140 } 1141 return true; 1142 } 1143 1144 /** Returns true if filtering_id in inclusive range of 0-255^maxBytes. */ isValidFilteringId( JSONObject value, Integer maxBytes, AsyncFetchStatus asyncFetchStatus)1145 private boolean isValidFilteringId( 1146 JSONObject value, Integer maxBytes, AsyncFetchStatus asyncFetchStatus) { 1147 Optional<UnsignedLong> maybeFilteringId = 1148 FetcherUtil.extractUnsignedLong(value, AggregatableKeyValueContract.FILTERING_ID); 1149 if (maybeFilteringId.isEmpty()) { 1150 LoggerFactory.getMeasurementLogger() 1151 .e( 1152 String.format( 1153 "AGGREGATABLE_VALUES: filtering_id is not an unsigned long" 1154 + " string")); 1155 return false; 1156 } 1157 BigInteger filteringId = new BigInteger(maybeFilteringId.get().toString()); 1158 if (filteringId.compareTo(BigInteger.valueOf(ONE_BYTE).pow(maxBytes)) >= 0) { 1159 LoggerFactory.getMeasurementLogger() 1160 .e(String.format("AGGREGATABLE_VALUES: filtering_id is out of bounds")); 1161 return false; 1162 } 1163 asyncFetchStatus.setIsTriggerFilteringIdConfigured(true); 1164 return true; 1165 } 1166 1167 /* 1168 * Returns true if JSONObject has valid value and filtering_id 1169 * Input looks like: {“value”: 32768, “filtering_id”: 123} 1170 */ isValidAggregateValueObj( JSONObject obj, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1171 private boolean isValidAggregateValueObj( 1172 JSONObject obj, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus) 1173 throws JSONException { 1174 // Validate value 1175 if (obj.isNull(AggregatableKeyValueContract.VALUE) 1176 || !isValidAggregatableValuesValue(obj.get(AggregatableKeyValueContract.VALUE))) { 1177 LoggerFactory.getMeasurementLogger() 1178 .d( 1179 String.format( 1180 "Aggregatable Values: Null or invalid %s", 1181 AggregatableKeyValueContract.VALUE)); 1182 return false; 1183 } 1184 // Validate filtering_id 1185 if (!obj.isNull(AggregatableKeyValueContract.FILTERING_ID) 1186 && (!isValidFilteringId(obj, filteringIdMaxBytes, asyncFetchStatus))) { 1187 return false; 1188 } 1189 return true; 1190 } 1191 1192 /** Returns filtering_id_max_bytes if in inclusive range 1-8. */ getValidAggregatableFilteringIdMaxBytes(JSONObject headers)1193 private Optional<Integer> getValidAggregatableFilteringIdMaxBytes(JSONObject headers) { 1194 Optional<BigDecimal> maybeValidAggregatableFilteringIdMaxBytes = 1195 FetcherUtil.extractIntegralValue( 1196 headers, TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES); 1197 if (maybeValidAggregatableFilteringIdMaxBytes.isPresent()) { 1198 BigDecimal lowerBound = new BigDecimal("1"); 1199 BigDecimal upperBound = new BigDecimal(mFlags.getMeasurementMaxFilteringIdMaxBytes()); 1200 if (maybeValidAggregatableFilteringIdMaxBytes.get().compareTo(lowerBound) < 0 1201 || maybeValidAggregatableFilteringIdMaxBytes.get().compareTo(upperBound) > 0) { 1202 LoggerFactory.getMeasurementLogger() 1203 .e("AGGREGATABLE_FILTERING_ID_MAX_BYTES is out of bounds."); 1204 return Optional.empty(); 1205 } 1206 return Optional.of(maybeValidAggregatableFilteringIdMaxBytes.get().intValue()); 1207 } 1208 return Optional.empty(); 1209 } 1210 getValidAggregateDuplicationKeysString( JSONArray aggregateDeduplicationKeys)1211 private Optional<String> getValidAggregateDuplicationKeysString( 1212 JSONArray aggregateDeduplicationKeys) throws JSONException { 1213 JSONArray validAggregateDeduplicationKeys = new JSONArray(); 1214 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 1215 if (shouldCheckFilterSize 1216 && aggregateDeduplicationKeys.length() 1217 > mFlags.getMeasurementMaxAggregateDeduplicationKeysPerRegistration()) { 1218 LoggerFactory.getMeasurementLogger() 1219 .d( 1220 "Aggregate deduplication keys have more keys than permitted. %s", 1221 aggregateDeduplicationKeys.length()); 1222 return Optional.empty(); 1223 } 1224 for (int i = 0; i < aggregateDeduplicationKeys.length(); i++) { 1225 JSONObject aggregateDedupKey = new JSONObject(); 1226 JSONObject deduplicationKeyObj = aggregateDeduplicationKeys.getJSONObject(i); 1227 1228 if (!deduplicationKeyObj.isNull("deduplication_key")) { 1229 Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong( 1230 deduplicationKeyObj, "deduplication_key"); 1231 if (!maybeDedupKey.isPresent()) { 1232 return Optional.empty(); 1233 } 1234 aggregateDedupKey.put("deduplication_key", maybeDedupKey.get().toString()); 1235 } 1236 if (!deduplicationKeyObj.isNull(FilterContract.FILTERS)) { 1237 JSONArray filters = 1238 Filter.maybeWrapFilters(deduplicationKeyObj, FilterContract.FILTERS); 1239 if (!FetcherUtil.areValidAttributionFilters( 1240 filters, 1241 mFlags, 1242 /* canIncludeLookbackWindow= */ true, 1243 shouldCheckFilterSize)) { 1244 LoggerFactory.getMeasurementLogger() 1245 .d("Aggregate deduplication key: " + i + " contains invalid filters."); 1246 return Optional.empty(); 1247 } 1248 aggregateDedupKey.put(FilterContract.FILTERS, filters); 1249 } 1250 if (!deduplicationKeyObj.isNull(FilterContract.NOT_FILTERS)) { 1251 JSONArray notFilters = 1252 Filter.maybeWrapFilters(deduplicationKeyObj, FilterContract.NOT_FILTERS); 1253 if (!FetcherUtil.areValidAttributionFilters( 1254 notFilters, 1255 mFlags, 1256 /* canIncludeLookbackWindow= */ true, 1257 shouldCheckFilterSize)) { 1258 LoggerFactory.getMeasurementLogger() 1259 .d( 1260 "Aggregate deduplication key: " 1261 + i 1262 + " contains invalid not filters."); 1263 return Optional.empty(); 1264 } 1265 aggregateDedupKey.put(FilterContract.NOT_FILTERS, notFilters); 1266 } 1267 validAggregateDeduplicationKeys.put(aggregateDedupKey); 1268 } 1269 return Optional.of(validAggregateDeduplicationKeys.toString()); 1270 } 1271 extractValidAttributionConfigs(JSONArray attributionConfigsArray)1272 private String extractValidAttributionConfigs(JSONArray attributionConfigsArray) 1273 throws JSONException { 1274 JSONArray validAttributionConfigsArray = new JSONArray(); 1275 for (int i = 0; i < attributionConfigsArray.length(); i++) { 1276 AttributionConfig attributionConfig = 1277 new AttributionConfig.Builder(attributionConfigsArray.getJSONObject(i), mFlags) 1278 .build(); 1279 validAttributionConfigsArray.put(attributionConfig.serializeAsJson(mFlags)); 1280 } 1281 return validAttributionConfigsArray.toString(); 1282 } 1283 isValidXNetworkKeyMapping(JSONObject adTechBitMapping)1284 private boolean isValidXNetworkKeyMapping(JSONObject adTechBitMapping) throws JSONException { 1285 // TODO: Might need to add logic for keys' and values' lengths. 1286 Iterator<String> keys = adTechBitMapping.keys(); 1287 while (keys.hasNext()) { 1288 String key = keys.next(); 1289 String value = adTechBitMapping.optString(key); 1290 if (value == null || !value.startsWith("0x")) { 1291 return false; 1292 } 1293 } 1294 return true; 1295 } 1296 getAttributionDestination( Uri destination, AsyncRegistration.RegistrationType registrationType)1297 private static Uri getAttributionDestination( 1298 Uri destination, AsyncRegistration.RegistrationType registrationType) { 1299 return registrationType == AsyncRegistration.RegistrationType.APP_TRIGGER 1300 ? BaseUriExtractor.getBaseUri(destination) 1301 : destination; 1302 } 1303 1304 // TODO(b/311183933): Remove passed in Context from static method. 1305 @SuppressWarnings("AvoidStaticContext") getOdpDelegationManager(Context context, Flags flags)1306 private static IOdpDelegationWrapper getOdpDelegationManager(Context context, Flags flags) { 1307 if (!SdkLevel.isAtLeastT() || !flags.getMeasurementEnableOdpWebTriggerRegistration()) { 1308 return new NoOdpDelegationWrapper(); 1309 } 1310 1311 OnDevicePersonalizationSystemEventManager odpSystemEventManager = null; 1312 try { 1313 odpSystemEventManager = 1314 context.getSystemService(OnDevicePersonalizationSystemEventManager.class); 1315 } catch (Exception e) { 1316 LoggerFactory.getMeasurementLogger().d(e, "getOdpDelegationManager: Unknown Exception"); 1317 ErrorLogUtil.e( 1318 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR, 1319 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 1320 } 1321 return (odpSystemEventManager != null) 1322 ? new OdpDelegationWrapperImpl(odpSystemEventManager) 1323 : new NoOdpDelegationWrapper(); 1324 } 1325 logInvalidTriggerField(String field, String enrollmentId, String triggerId)1326 private void logInvalidTriggerField(String field, String enrollmentId, String triggerId) { 1327 LoggerFactory.getMeasurementLogger() 1328 .d( 1329 "AsyncTriggerFetcher: Invalid %s. Enrollment ID: %s, Trigger ID: %s", 1330 field, enrollmentId, triggerId); 1331 } 1332 1333 private interface TriggerHeaderContract { 1334 String HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER = 1335 "Attribution-Reporting-Register-Trigger"; 1336 // Header for verbose debug reports. 1337 String HEADER_ATTRIBUTION_REPORTING_INFO = "Attribution-Reporting-Info"; 1338 String ATTRIBUTION_CONFIG = "attribution_config"; 1339 String EVENT_TRIGGER_DATA = "event_trigger_data"; 1340 String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data"; 1341 String AGGREGATABLE_VALUES = "aggregatable_values"; 1342 String AGGREGATABLE_DEDUPLICATION_KEYS = "aggregatable_deduplication_keys"; 1343 String DEBUG_KEY = "debug_key"; 1344 String DEBUG_REPORTING = "debug_reporting"; 1345 String X_NETWORK_KEY_MAPPING = "x_network_key_mapping"; 1346 String DEBUG_JOIN_KEY = "debug_join_key"; 1347 String DEBUG_AD_ID = "debug_ad_id"; 1348 String AGGREGATION_COORDINATOR_ORIGIN = "aggregation_coordinator_origin"; 1349 String AGGREGATABLE_SOURCE_REGISTRATION_TIME = "aggregatable_source_registration_time"; 1350 String TRIGGER_CONTEXT_ID = "trigger_context_id"; 1351 String ATTRIBUTION_SCOPES = "attribution_scopes"; 1352 String AGGREGATABLE_DEBUG_REPORTING = "aggregatable_debug_reporting"; 1353 String AGGREGATABLE_FILTERING_ID_MAX_BYTES = "aggregatable_filtering_id_max_bytes"; 1354 } 1355 1356 private interface OdpTriggerHeaderContract { 1357 String HEADER_ODP_REGISTER_TRIGGER = "Odp-Register-Trigger"; 1358 } 1359 } 1360