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.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID; 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT; 21 22 import android.adservices.ondevicepersonalization.OnDevicePersonalizationSystemEventManager; 23 import android.annotation.NonNull; 24 import android.content.Context; 25 import android.net.Uri; 26 27 import com.android.adservices.LoggerFactory; 28 import com.android.adservices.data.enrollment.EnrollmentDao; 29 import com.android.adservices.data.measurement.DatastoreManager; 30 import com.android.adservices.data.measurement.DatastoreManagerFactory; 31 import com.android.adservices.errorlogging.ErrorLogUtil; 32 import com.android.adservices.service.Flags; 33 import com.android.adservices.service.FlagsFactory; 34 import com.android.adservices.service.common.AllowLists; 35 import com.android.adservices.service.common.WebAddresses; 36 import com.android.adservices.service.measurement.AttributionConfig; 37 import com.android.adservices.service.measurement.EventSurfaceType; 38 import com.android.adservices.service.measurement.MeasurementHttpClient; 39 import com.android.adservices.service.measurement.Trigger; 40 import com.android.adservices.service.measurement.TriggerSpecs; 41 import com.android.adservices.service.measurement.XNetworkData; 42 import com.android.adservices.service.measurement.ondevicepersonalization.IOdpDelegationWrapper; 43 import com.android.adservices.service.measurement.ondevicepersonalization.NoOdpDelegationWrapper; 44 import com.android.adservices.service.measurement.ondevicepersonalization.OdpDelegationWrapperImpl; 45 import com.android.adservices.service.measurement.reporting.DebugReportApi; 46 import com.android.adservices.service.measurement.util.BaseUriExtractor; 47 import com.android.adservices.service.measurement.util.Enrollment; 48 import com.android.adservices.service.measurement.util.Filter; 49 import com.android.adservices.service.measurement.util.UnsignedLong; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.modules.utils.build.SdkLevel; 52 53 import org.json.JSONArray; 54 import org.json.JSONException; 55 import org.json.JSONObject; 56 57 import java.io.IOException; 58 import java.net.HttpURLConnection; 59 import java.net.MalformedURLException; 60 import java.net.URL; 61 import java.net.URLConnection; 62 import java.util.HashSet; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Locale; 66 import java.util.Map; 67 import java.util.Optional; 68 import java.util.Set; 69 70 /** 71 * Download and decode Trigger registration. 72 * 73 * @hide 74 */ 75 public class AsyncTriggerFetcher { 76 77 private final MeasurementHttpClient mNetworkConnection; 78 private final EnrollmentDao mEnrollmentDao; 79 private final Flags mFlags; 80 private final Context mContext; 81 private final IOdpDelegationWrapper mOdpWrapper; 82 private final DatastoreManager mDatastoreManager; 83 private final DebugReportApi mDebugReportApi; 84 AsyncTriggerFetcher(Context context)85 public AsyncTriggerFetcher(Context context) { 86 this( 87 context, 88 EnrollmentDao.getInstance(), 89 FlagsFactory.getFlags(), 90 getOdpDelegationManager(context, FlagsFactory.getFlags()), 91 DatastoreManagerFactory.getDatastoreManager(context), 92 new DebugReportApi(context, FlagsFactory.getFlags())); 93 } 94 95 @VisibleForTesting AsyncTriggerFetcher( Context context, EnrollmentDao enrollmentDao, Flags flags, IOdpDelegationWrapper odpWrapper, DatastoreManager datastoreManager, DebugReportApi debugReportApi)96 public AsyncTriggerFetcher( 97 Context context, 98 EnrollmentDao enrollmentDao, 99 Flags flags, 100 IOdpDelegationWrapper odpWrapper, 101 DatastoreManager datastoreManager, 102 DebugReportApi debugReportApi) { 103 mContext = context; 104 mEnrollmentDao = enrollmentDao; 105 mFlags = flags; 106 mNetworkConnection = new MeasurementHttpClient(context); 107 mDatastoreManager = datastoreManager; 108 mDebugReportApi = debugReportApi; 109 mOdpWrapper = odpWrapper; 110 } 111 112 /** 113 * Parse a {@code Trigger}, given response headers, adding the {@code Trigger} to a given list. 114 */ 115 @VisibleForTesting parseTrigger( AsyncRegistration asyncRegistration, String enrollmentId, Map<String, List<String>> headers, AsyncFetchStatus asyncFetchStatus)116 public Optional<Trigger> parseTrigger( 117 AsyncRegistration asyncRegistration, 118 String enrollmentId, 119 Map<String, List<String>> headers, 120 AsyncFetchStatus asyncFetchStatus) { 121 boolean arDebugPermission = asyncRegistration.getDebugKeyAllowed(); 122 LoggerFactory.getMeasurementLogger() 123 .d("Trigger ArDebug permission enabled %b", arDebugPermission); 124 Trigger.Builder builder = new Trigger.Builder(); 125 builder.setEnrollmentId(enrollmentId); 126 builder.setAttributionDestination( 127 getAttributionDestination( 128 asyncRegistration.getTopOrigin(), asyncRegistration.getType())); 129 builder.setRegistrant(asyncRegistration.getRegistrant()); 130 builder.setAdIdPermission(asyncRegistration.hasAdIdPermission()); 131 builder.setArDebugPermission(arDebugPermission); 132 builder.setDestinationType( 133 asyncRegistration.isWebRequest() ? EventSurfaceType.WEB : EventSurfaceType.APP); 134 builder.setTriggerTime(asyncRegistration.getRequestTime()); 135 Optional<Uri> registrationUriOrigin = 136 WebAddresses.originAndScheme(asyncRegistration.getRegistrationUri()); 137 if (!registrationUriOrigin.isPresent()) { 138 LoggerFactory.getMeasurementLogger() 139 .d( 140 "AsyncTriggerFetcher: " 141 + "Invalid or empty registration uri - " 142 + asyncRegistration.getRegistrationUri()); 143 return Optional.empty(); 144 } 145 builder.setRegistrationOrigin(registrationUriOrigin.get()); 146 147 builder.setPlatformAdId(asyncRegistration.getPlatformAdId()); 148 149 List<String> field = 150 headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 151 if (field == null || field.size() != 1) { 152 LoggerFactory.getMeasurementLogger() 153 .d( 154 "AsyncTriggerFetcher: " 155 + "Invalid " 156 + TriggerHeaderContract 157 .HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER 158 + " header."); 159 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_ERROR); 160 return Optional.empty(); 161 } 162 String registrationHeaderStr = field.get(0); 163 164 boolean isHeaderErrorDebugReportEnabled = 165 FetcherUtil.isHeaderErrorDebugReportEnabled( 166 headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_INFO), 167 mFlags); 168 try { 169 String eventTriggerData = new JSONArray().toString(); 170 JSONObject json = new JSONObject(registrationHeaderStr); 171 if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) { 172 Optional<String> validEventTriggerData = 173 getValidEventTriggerData( 174 json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA)); 175 if (!validEventTriggerData.isPresent()) { 176 asyncFetchStatus.setEntityStatus( 177 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 178 return Optional.empty(); 179 } 180 eventTriggerData = validEventTriggerData.get(); 181 } 182 builder.setEventTriggers(eventTriggerData); 183 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) { 184 Optional<String> validAggregateTriggerData = 185 getValidAggregateTriggerData( 186 json.getJSONArray(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)); 187 if (!validAggregateTriggerData.isPresent()) { 188 asyncFetchStatus.setEntityStatus( 189 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 190 return Optional.empty(); 191 } 192 builder.setAggregateTriggerData(validAggregateTriggerData.get()); 193 } 194 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_VALUES)) { 195 if (!isValidAggregateValues( 196 json.getJSONObject(TriggerHeaderContract.AGGREGATABLE_VALUES))) { 197 asyncFetchStatus.setEntityStatus( 198 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 199 return Optional.empty(); 200 } 201 builder.setAggregateValues( 202 json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES)); 203 } 204 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)) { 205 Optional<String> validAggregateDeduplicationKeysString = 206 getValidAggregateDuplicationKeysString( 207 json.getJSONArray( 208 TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)); 209 if (!validAggregateDeduplicationKeysString.isPresent()) { 210 LoggerFactory.getMeasurementLogger() 211 .d("parseTrigger: aggregate deduplication keys are invalid."); 212 asyncFetchStatus.setEntityStatus( 213 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 214 return Optional.empty(); 215 } 216 builder.setAggregateDeduplicationKeys(validAggregateDeduplicationKeysString.get()); 217 } 218 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 219 if (!json.isNull(TriggerHeaderContract.FILTERS)) { 220 JSONArray filters = Filter.maybeWrapFilters(json, TriggerHeaderContract.FILTERS); 221 if (!FetcherUtil.areValidAttributionFilters( 222 filters, mFlags, true, shouldCheckFilterSize)) { 223 LoggerFactory.getMeasurementLogger().d("parseTrigger: filters are invalid."); 224 asyncFetchStatus.setEntityStatus( 225 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 226 return Optional.empty(); 227 } 228 builder.setFilters(filters.toString()); 229 } 230 if (!json.isNull(TriggerHeaderContract.NOT_FILTERS)) { 231 JSONArray notFilters = 232 Filter.maybeWrapFilters(json, TriggerHeaderContract.NOT_FILTERS); 233 if (!FetcherUtil.areValidAttributionFilters( 234 notFilters, mFlags, true, shouldCheckFilterSize)) { 235 LoggerFactory.getMeasurementLogger() 236 .d("parseTrigger: not-filters are invalid."); 237 asyncFetchStatus.setEntityStatus( 238 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 239 return Optional.empty(); 240 } 241 builder.setNotFilters(notFilters.toString()); 242 } 243 if (!json.isNull(TriggerHeaderContract.DEBUG_REPORTING)) { 244 builder.setIsDebugReporting(json.optBoolean(TriggerHeaderContract.DEBUG_REPORTING)); 245 } 246 if (!json.isNull(TriggerHeaderContract.DEBUG_KEY)) { 247 Optional<UnsignedLong> maybeDebugKey = 248 FetcherUtil.extractUnsignedLong(json, TriggerHeaderContract.DEBUG_KEY); 249 if (maybeDebugKey.isPresent()) { 250 builder.setDebugKey(maybeDebugKey.get()); 251 } 252 } 253 if (mFlags.getMeasurementEnableXNA() 254 && !json.isNull(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)) { 255 if (!isValidXNetworkKeyMapping( 256 json.getJSONObject(TriggerHeaderContract.X_NETWORK_KEY_MAPPING))) { 257 LoggerFactory.getMeasurementLogger() 258 .d("parseTrigger: adtech bit mapping is invalid."); 259 } else { 260 builder.setAdtechBitMapping( 261 json.getString(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)); 262 } 263 } 264 if (mFlags.getMeasurementEnableXNA() 265 && isXnaAllowedForTriggerRegistrant( 266 asyncRegistration.getRegistrant(), asyncRegistration.getType()) 267 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_CONFIG)) { 268 String attributionConfigsString = 269 extractValidAttributionConfigs( 270 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_CONFIG)); 271 builder.setAttributionConfig(attributionConfigsString); 272 } 273 274 if (mFlags.getMeasurementAggregationCoordinatorOriginEnabled() 275 && !json.isNull(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN)) { 276 String origin = 277 json.getString(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN); 278 String allowlist = mFlags.getMeasurementAggregationCoordinatorOriginList(); 279 if (origin.isEmpty() || !isAllowlisted(allowlist, origin)) { 280 LoggerFactory.getMeasurementLogger() 281 .d("parseTrigger: aggregation_coordinator_origin is invalid."); 282 asyncFetchStatus.setEntityStatus( 283 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 284 return Optional.empty(); 285 } 286 builder.setAggregationCoordinatorOrigin(Uri.parse(origin)); 287 } 288 289 String enrollmentBlockList = 290 mFlags.getMeasurementPlatformDebugAdIdMatchingEnrollmentBlocklist(); 291 Set<String> blockedEnrollmentsString = 292 new HashSet<>(AllowLists.splitAllowList(enrollmentBlockList)); 293 if (!AllowLists.doesAllowListAllowAll(enrollmentBlockList) 294 && !blockedEnrollmentsString.contains(enrollmentId) 295 && !json.isNull(TriggerHeaderContract.DEBUG_AD_ID)) { 296 builder.setDebugAdId(json.optString(TriggerHeaderContract.DEBUG_AD_ID)); 297 } 298 299 Set<String> allowedEnrollmentsString = 300 new HashSet<>( 301 AllowLists.splitAllowList( 302 mFlags.getMeasurementDebugJoinKeyEnrollmentAllowlist())); 303 if (allowedEnrollmentsString.contains(enrollmentId) 304 && !json.isNull(TriggerHeaderContract.DEBUG_JOIN_KEY)) { 305 builder.setDebugJoinKey(json.optString(TriggerHeaderContract.DEBUG_JOIN_KEY)); 306 } 307 308 Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig = 309 getSourceRegistrationTimeConfig(json); 310 311 builder.setAggregatableSourceRegistrationTimeConfig(sourceRegistrationTimeConfig); 312 313 if (mFlags.getMeasurementEnableTriggerContextId() 314 && !json.isNull(TriggerHeaderContract.TRIGGER_CONTEXT_ID)) { 315 if (Trigger.SourceRegistrationTimeConfig.INCLUDE.equals( 316 sourceRegistrationTimeConfig)) { 317 LoggerFactory.getMeasurementLogger() 318 .d( 319 "parseTrigger: %s cannot be set when %s has a value of %s", 320 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 321 TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME, 322 Trigger.SourceRegistrationTimeConfig.INCLUDE.name()); 323 asyncFetchStatus.setEntityStatus( 324 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 325 return Optional.empty(); 326 } 327 328 Optional<String> contextIdOpt = getValidTriggerContextId(json); 329 if (contextIdOpt.isEmpty()) { 330 asyncFetchStatus.setEntityStatus( 331 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 332 return Optional.empty(); 333 } 334 335 builder.setTriggerContextId(contextIdOpt.get()); 336 } 337 338 if (mFlags.getMeasurementEnableAttributionScope() 339 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_SCOPES)) { 340 Optional<List<String>> attributionScopes = 341 FetcherUtil.extractStringArray( 342 json, 343 TriggerHeaderContract.ATTRIBUTION_SCOPES, 344 mFlags.getMeasurementMaxAttributionScopesPerSource(), 345 mFlags.getMeasurementMaxAttributionScopeLength()); 346 if (attributionScopes.isEmpty() || attributionScopes.get().isEmpty()) { 347 LoggerFactory.getMeasurementLogger() 348 .e("parseTrigger: attribution_scopes is invalid."); 349 asyncFetchStatus.setEntityStatus( 350 AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 351 return Optional.empty(); 352 } 353 builder.setAttributionScopesString( 354 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_SCOPES).toString()); 355 } 356 357 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.SUCCESS); 358 return Optional.of(builder.build()); 359 } catch (JSONException e) { 360 String errMsg = "Trigger JSON parsing failed"; 361 LoggerFactory.getMeasurementLogger().e(e, errMsg); 362 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.PARSING_ERROR); 363 if (isHeaderErrorDebugReportEnabled) { 364 mDatastoreManager.runInTransaction( 365 (dao) -> { 366 mDebugReportApi.scheduleHeaderErrorReport( 367 asyncRegistration.getRegistrationUri(), 368 asyncRegistration.getRegistrant(), 369 TriggerHeaderContract 370 .HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER, 371 enrollmentId, 372 errMsg, 373 registrationHeaderStr, 374 dao); 375 }); 376 } 377 return Optional.empty(); 378 } catch (IllegalArgumentException e) { 379 LoggerFactory.getMeasurementLogger() 380 .d(e, "AsyncTriggerFetcher: IllegalArgumentException"); 381 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.VALIDATION_ERROR); 382 return Optional.empty(); 383 } 384 } 385 getValidTriggerContextId(JSONObject json)386 private Optional<String> getValidTriggerContextId(JSONObject json) throws JSONException { 387 Object triggerContextIdObj = json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID); 388 if (!(triggerContextIdObj instanceof String)) { 389 LoggerFactory.getMeasurementLogger() 390 .d( 391 "%s: %s, is not a String", 392 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 393 json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID).toString()); 394 return Optional.empty(); 395 } 396 397 String contextId = triggerContextIdObj.toString(); 398 if (contextId.length() > mFlags.getMeasurementMaxLengthOfTriggerContextId()) { 399 LoggerFactory.getMeasurementLogger() 400 .d( 401 "Length of %s: \"%s\", exceeds max length of %d", 402 TriggerHeaderContract.TRIGGER_CONTEXT_ID, 403 contextId, 404 mFlags.getMeasurementMaxLengthOfTriggerContextId()); 405 return Optional.empty(); 406 } 407 408 return Optional.of(contextId); 409 } 410 getSourceRegistrationTimeConfig(JSONObject json)411 private Trigger.SourceRegistrationTimeConfig getSourceRegistrationTimeConfig(JSONObject json) { 412 Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig = 413 Trigger.SourceRegistrationTimeConfig.INCLUDE; 414 415 if (mFlags.getMeasurementSourceRegistrationTimeOptionalForAggReportsEnabled()) { 416 sourceRegistrationTimeConfig = Trigger.SourceRegistrationTimeConfig.EXCLUDE; 417 if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME)) { 418 String sourceRegistrationTimeConfigString = 419 json.optString(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME) 420 .toUpperCase(Locale.ENGLISH); 421 sourceRegistrationTimeConfig = 422 Trigger.SourceRegistrationTimeConfig.valueOf( 423 sourceRegistrationTimeConfigString); 424 } 425 } 426 427 return sourceRegistrationTimeConfig; 428 } 429 isAllowlisted(String allowlist, String origin)430 private boolean isAllowlisted(String allowlist, String origin) { 431 if (AllowLists.doesAllowListAllowAll(allowlist)) { 432 return true; 433 } 434 Set<String> elements = new HashSet<>(AllowLists.splitAllowList(allowlist)); 435 return elements.contains(origin); 436 } 437 438 /** Provided a testing hook. */ 439 @NonNull openUrl(@onNull URL url)440 public URLConnection openUrl(@NonNull URL url) throws IOException { 441 return mNetworkConnection.setup(url); 442 } 443 isXnaAllowedForTriggerRegistrant( Uri registrant, AsyncRegistration.RegistrationType registrationType)444 private boolean isXnaAllowedForTriggerRegistrant( 445 Uri registrant, AsyncRegistration.RegistrationType registrationType) { 446 // If the trigger is registered from web context, only allow-listed apps should be able to 447 // parse attribution config. 448 return !AsyncRegistration.RegistrationType.WEB_TRIGGER.equals(registrationType) 449 || AllowLists.isPackageAllowListed( 450 mFlags.getWebContextClientAppAllowList(), registrant.getAuthority()); 451 } 452 453 /** 454 * Fetch a trigger type registration. 455 * 456 * @param asyncRegistration a {@link AsyncRegistration}, a request the record. 457 * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status. 458 * @param asyncRedirects a {@link AsyncRedirects}, stores redirections. 459 */ fetchTrigger( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirects asyncRedirects)460 public Optional<Trigger> fetchTrigger( 461 AsyncRegistration asyncRegistration, 462 AsyncFetchStatus asyncFetchStatus, 463 AsyncRedirects asyncRedirects) { 464 HttpURLConnection urlConnection = null; 465 Map<String, List<String>> headers; 466 if (!asyncRegistration.getRegistrationUri().getScheme().equalsIgnoreCase("https")) { 467 LoggerFactory.getMeasurementLogger().d("Invalid scheme for registrationUri."); 468 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL); 469 return Optional.empty(); 470 } 471 // TODO(b/276825561): Fix code duplication between fetchSource & fetchTrigger request flow 472 Optional<String> enrollmentId = Optional.empty(); 473 try { 474 urlConnection = 475 (HttpURLConnection) 476 openUrl(new URL(asyncRegistration.getRegistrationUri().toString())); 477 urlConnection.setRequestMethod("POST"); 478 urlConnection.setInstanceFollowRedirects(false); 479 headers = urlConnection.getHeaderFields(); 480 enrollmentId = getEnrollmentId(asyncRegistration); 481 482 // get ODP header from headers map and forward ODP header 483 long odpHeaderSize = 0; 484 Optional<Map<String, List<String>>> odpHeader = getOdpTriggerHeader(headers); 485 if (odpHeader.isPresent()) { 486 mOdpWrapper.registerOdpTrigger( 487 asyncRegistration, odpHeader.get(), enrollmentId.isPresent()); 488 odpHeaderSize = FetcherUtil.calculateHeadersCharactersLength(odpHeader.get()); 489 } 490 491 long headerSize = FetcherUtil.calculateHeadersCharactersLength(headers) - odpHeaderSize; 492 if (mFlags.getMeasurementEnableUpdateTriggerHeaderLimit() 493 && headerSize > mFlags.getMaxTriggerRegistrationHeaderSizeBytes()) { 494 LoggerFactory.getMeasurementLogger() 495 .d( 496 "Trigger registration header size exceeds limit bytes " 497 + mFlags.getMaxTriggerRegistrationHeaderSizeBytes()); 498 asyncFetchStatus.setResponseStatus( 499 AsyncFetchStatus.ResponseStatus.HEADER_SIZE_LIMIT_EXCEEDED); 500 return Optional.empty(); 501 } 502 asyncFetchStatus.setResponseSize(headerSize); 503 int responseCode = urlConnection.getResponseCode(); 504 LoggerFactory.getMeasurementLogger().d("Response code = " + responseCode); 505 if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) { 506 asyncFetchStatus.setResponseStatus( 507 AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE); 508 return Optional.empty(); 509 } 510 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.SUCCESS); 511 } catch (MalformedURLException e) { 512 LoggerFactory.getMeasurementLogger().d(e, "Malformed registration target URL"); 513 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL); 514 return Optional.empty(); 515 } catch (IOException e) { 516 LoggerFactory.getMeasurementLogger().d(e, "Failed to get registration response"); 517 asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR); 518 return Optional.empty(); 519 } finally { 520 if (urlConnection != null) { 521 urlConnection.disconnect(); 522 } 523 } 524 525 asyncRedirects.configure(headers, mFlags, asyncRegistration); 526 527 if (!isTriggerHeaderPresent(headers)) { 528 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_MISSING); 529 asyncFetchStatus.setRedirectOnlyStatus(true); 530 return Optional.empty(); 531 } 532 533 if (enrollmentId.isEmpty()) { 534 LoggerFactory.getMeasurementLogger() 535 .d( 536 "fetchTrigger: Valid enrollment id not found. Registration URI: %s", 537 asyncRegistration.getRegistrationUri()); 538 asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.INVALID_ENROLLMENT); 539 ErrorLogUtil.e( 540 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID, 541 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 542 return Optional.empty(); 543 } 544 return parseTrigger(asyncRegistration, enrollmentId.get(), headers, asyncFetchStatus); 545 } 546 547 /** Return instance of IOdpDelegationWrapper. */ getOdpWrapper()548 public IOdpDelegationWrapper getOdpWrapper() { 549 return mOdpWrapper; 550 } 551 isTriggerHeaderPresent(Map<String, List<String>> headers)552 private boolean isTriggerHeaderPresent(Map<String, List<String>> headers) { 553 return headers.containsKey( 554 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER); 555 } 556 getOdpTriggerHeader( Map<String, List<String>> headers)557 private Optional<Map<String, List<String>>> getOdpTriggerHeader( 558 Map<String, List<String>> headers) { 559 return headers.containsKey(OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER) 560 ? Optional.of(Map.of( 561 OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER, 562 headers.get( 563 OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER))) 564 : Optional.empty(); 565 } 566 getEnrollmentId(AsyncRegistration asyncRegistration)567 private Optional<String> getEnrollmentId(AsyncRegistration asyncRegistration) { 568 return mFlags.isDisableMeasurementEnrollmentCheck() 569 ? WebAddresses.topPrivateDomainAndScheme(asyncRegistration.getRegistrationUri()) 570 .map(Uri::toString) 571 : Enrollment.getValidEnrollmentId( 572 asyncRegistration.getRegistrationUri(), 573 asyncRegistration.getRegistrant().getAuthority(), 574 mEnrollmentDao, 575 mContext, 576 mFlags); 577 } 578 getValidEventTriggerData(JSONArray eventTriggerDataArr)579 private Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) { 580 JSONArray validEventTriggerData = new JSONArray(); 581 for (int i = 0; i < eventTriggerDataArr.length(); i++) { 582 JSONObject validEventTriggerDatum = new JSONObject(); 583 try { 584 JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i); 585 UnsignedLong triggerData = new UnsignedLong(0L); 586 if (!eventTriggerDatum.isNull("trigger_data")) { 587 Optional<UnsignedLong> maybeTriggerData = 588 FetcherUtil.extractUnsignedLong(eventTriggerDatum, "trigger_data"); 589 if (!maybeTriggerData.isPresent()) { 590 return Optional.empty(); 591 } 592 triggerData = maybeTriggerData.get(); 593 } 594 validEventTriggerDatum.put("trigger_data", triggerData); 595 if (!eventTriggerDatum.isNull("priority")) { 596 Optional<Long> maybePriority = 597 FetcherUtil.extractLongString(eventTriggerDatum, "priority"); 598 if (!maybePriority.isPresent()) { 599 return Optional.empty(); 600 } 601 validEventTriggerDatum.put("priority", String.valueOf(maybePriority.get())); 602 } 603 if (!eventTriggerDatum.isNull("value")) { 604 Optional<Long> maybeValue = 605 FetcherUtil.extractLong(eventTriggerDatum, "value"); 606 if (!maybeValue.isPresent()) { 607 return Optional.empty(); 608 } 609 long value = maybeValue.get(); 610 if (value < 1L || value > TriggerSpecs.MAX_BUCKET_THRESHOLD) { 611 return Optional.empty(); 612 } 613 validEventTriggerDatum.put("value", value); 614 } 615 if (!eventTriggerDatum.isNull("deduplication_key")) { 616 Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong( 617 eventTriggerDatum, "deduplication_key"); 618 if (!maybeDedupKey.isPresent()) { 619 return Optional.empty(); 620 } 621 validEventTriggerDatum.put("deduplication_key", maybeDedupKey.get()); 622 } 623 boolean shouldCheckFilterSize = 624 !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 625 if (!eventTriggerDatum.isNull("filters")) { 626 JSONArray filters = Filter.maybeWrapFilters(eventTriggerDatum, "filters"); 627 if (!FetcherUtil.areValidAttributionFilters( 628 filters, 629 mFlags, 630 /* canIncludeLookbackWindow= */ true, 631 shouldCheckFilterSize)) { 632 LoggerFactory.getMeasurementLogger() 633 .d("getValidEventTriggerData: filters are invalid."); 634 return Optional.empty(); 635 } 636 validEventTriggerDatum.put("filters", filters); 637 } 638 if (!eventTriggerDatum.isNull("not_filters")) { 639 JSONArray notFilters = 640 Filter.maybeWrapFilters(eventTriggerDatum, "not_filters"); 641 if (!FetcherUtil.areValidAttributionFilters( 642 notFilters, 643 mFlags, 644 /* canIncludeLookbackWindow= */ true, 645 shouldCheckFilterSize)) { 646 LoggerFactory.getMeasurementLogger() 647 .d("getValidEventTriggerData: not-filters are invalid."); 648 return Optional.empty(); 649 } 650 validEventTriggerDatum.put("not_filters", notFilters); 651 } 652 validEventTriggerData.put(validEventTriggerDatum); 653 } catch (JSONException e) { 654 LoggerFactory.getMeasurementLogger() 655 .d(e, "AsyncTriggerFetcher: JSONException parsing event trigger datum."); 656 return Optional.empty(); 657 } catch (NumberFormatException e) { 658 LoggerFactory.getMeasurementLogger() 659 .d( 660 e, 661 "AsyncTriggerFetcher: NumberFormatException parsing event trigger " 662 + "datum."); 663 return Optional.empty(); 664 } 665 } 666 return Optional.of(validEventTriggerData.toString()); 667 } 668 getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)669 private Optional<String> getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr) 670 throws JSONException { 671 JSONArray validAggregateTriggerData = new JSONArray(); 672 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 673 for (int i = 0; i < aggregateTriggerDataArr.length(); i++) { 674 JSONObject aggregateTriggerData = aggregateTriggerDataArr.getJSONObject(i); 675 String keyPiece = aggregateTriggerData.optString("key_piece"); 676 if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece, mFlags)) { 677 LoggerFactory.getMeasurementLogger() 678 .d("Aggregate trigger data key-piece is invalid. %s", keyPiece); 679 return Optional.empty(); 680 } 681 JSONArray sourceKeys; 682 if (aggregateTriggerData.isNull("source_keys")) { 683 sourceKeys = new JSONArray(); 684 aggregateTriggerData.put("source_keys", sourceKeys); 685 } else { 686 // Registration will be rejected if source-keys is not a list 687 sourceKeys = aggregateTriggerData.getJSONArray("source_keys"); 688 } 689 if (shouldCheckFilterSize 690 && sourceKeys.length() 691 > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) { 692 LoggerFactory.getMeasurementLogger() 693 .d( 694 "Aggregate trigger data source-keys list has more entries " 695 + "than permitted."); 696 return Optional.empty(); 697 } 698 for (int j = 0; j < sourceKeys.length(); j++) { 699 Object sourceKey = sourceKeys.get(j); 700 if (!(sourceKey instanceof String) 701 || !FetcherUtil.isValidAggregateKeyId((String) sourceKey)) { 702 LoggerFactory.getMeasurementLogger() 703 .d("Aggregate trigger data source-key is invalid. %s", sourceKey); 704 return Optional.empty(); 705 } 706 } 707 if (!aggregateTriggerData.isNull("filters")) { 708 JSONArray filters = Filter.maybeWrapFilters(aggregateTriggerData, "filters"); 709 if (!FetcherUtil.areValidAttributionFilters( 710 filters, 711 mFlags, 712 /* canIncludeLookbackWindow= */ true, 713 shouldCheckFilterSize)) { 714 LoggerFactory.getMeasurementLogger() 715 .d("Aggregate trigger data filters are invalid."); 716 return Optional.empty(); 717 } 718 aggregateTriggerData.put("filters", filters); 719 } 720 if (!aggregateTriggerData.isNull("not_filters")) { 721 JSONArray notFilters = Filter.maybeWrapFilters(aggregateTriggerData, "not_filters"); 722 if (!FetcherUtil.areValidAttributionFilters( 723 notFilters, 724 mFlags, 725 /* canIncludeLookbackWindow= */ true, 726 shouldCheckFilterSize)) { 727 LoggerFactory.getMeasurementLogger() 728 .d("Aggregate trigger data not-filters are invalid."); 729 return Optional.empty(); 730 } 731 aggregateTriggerData.put("not_filters", notFilters); 732 } 733 if (!aggregateTriggerData.isNull("x_network_data")) { 734 JSONObject xNetworkDataJson = aggregateTriggerData.getJSONObject("x_network_data"); 735 // This is in order to validate the JSON parsing does not throw exception 736 new XNetworkData.Builder(xNetworkDataJson); 737 } 738 validAggregateTriggerData.put(aggregateTriggerData); 739 } 740 return Optional.of(validAggregateTriggerData.toString()); 741 } 742 isValidAggregateValues(JSONObject aggregateValues)743 private boolean isValidAggregateValues(JSONObject aggregateValues) throws JSONException { 744 if (!mFlags.getMeasurementEnableUpdateTriggerHeaderLimit() 745 && aggregateValues.length() 746 > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) { 747 LoggerFactory.getMeasurementLogger() 748 .d( 749 "Aggregate values have more keys than permitted. %s", 750 aggregateValues.length()); 751 return false; 752 } 753 Iterator<String> ids = aggregateValues.keys(); 754 while (ids.hasNext()) { 755 String id = ids.next(); 756 if (!FetcherUtil.isValidAggregateKeyId(id)) { 757 LoggerFactory.getMeasurementLogger() 758 .d("Aggregate values key ID is invalid. %s", id); 759 return false; 760 } 761 Object maybeInt = aggregateValues.get(id); 762 if (!(maybeInt instanceof Integer) 763 || ((Integer) maybeInt) < 1 764 || ((Integer) maybeInt) 765 > mFlags.getMeasurementMaxSumOfAggregateValuesPerSource()) { 766 LoggerFactory.getMeasurementLogger() 767 .d("Aggregate values '%s' is invalid. %s", id, maybeInt); 768 return false; 769 } 770 } 771 return true; 772 } 773 getValidAggregateDuplicationKeysString( JSONArray aggregateDeduplicationKeys)774 private Optional<String> getValidAggregateDuplicationKeysString( 775 JSONArray aggregateDeduplicationKeys) throws JSONException { 776 JSONArray validAggregateDeduplicationKeys = new JSONArray(); 777 boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit(); 778 if (shouldCheckFilterSize 779 && aggregateDeduplicationKeys.length() 780 > mFlags.getMeasurementMaxAggregateDeduplicationKeysPerRegistration()) { 781 LoggerFactory.getMeasurementLogger() 782 .d( 783 "Aggregate deduplication keys have more keys than permitted. %s", 784 aggregateDeduplicationKeys.length()); 785 return Optional.empty(); 786 } 787 for (int i = 0; i < aggregateDeduplicationKeys.length(); i++) { 788 JSONObject aggregateDedupKey = new JSONObject(); 789 JSONObject deduplicationKeyObj = aggregateDeduplicationKeys.getJSONObject(i); 790 791 if (!deduplicationKeyObj.isNull("deduplication_key")) { 792 Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong( 793 deduplicationKeyObj, "deduplication_key"); 794 if (!maybeDedupKey.isPresent()) { 795 return Optional.empty(); 796 } 797 aggregateDedupKey.put("deduplication_key", maybeDedupKey.get().toString()); 798 } 799 if (!deduplicationKeyObj.isNull("filters")) { 800 JSONArray filters = Filter.maybeWrapFilters(deduplicationKeyObj, "filters"); 801 if (!FetcherUtil.areValidAttributionFilters( 802 filters, 803 mFlags, 804 /* canIncludeLookbackWindow= */ true, 805 shouldCheckFilterSize)) { 806 LoggerFactory.getMeasurementLogger() 807 .d("Aggregate deduplication key: " + i + " contains invalid filters."); 808 return Optional.empty(); 809 } 810 aggregateDedupKey.put("filters", filters); 811 } 812 if (!deduplicationKeyObj.isNull("not_filters")) { 813 JSONArray notFilters = Filter.maybeWrapFilters(deduplicationKeyObj, "not_filters"); 814 if (!FetcherUtil.areValidAttributionFilters( 815 notFilters, 816 mFlags, 817 /* canIncludeLookbackWindow= */ true, 818 shouldCheckFilterSize)) { 819 LoggerFactory.getMeasurementLogger() 820 .d( 821 "Aggregate deduplication key: " 822 + i 823 + " contains invalid not filters."); 824 return Optional.empty(); 825 } 826 aggregateDedupKey.put("not_filters", notFilters); 827 } 828 validAggregateDeduplicationKeys.put(aggregateDedupKey); 829 } 830 return Optional.of(validAggregateDeduplicationKeys.toString()); 831 } 832 extractValidAttributionConfigs(JSONArray attributionConfigsArray)833 private String extractValidAttributionConfigs(JSONArray attributionConfigsArray) 834 throws JSONException { 835 JSONArray validAttributionConfigsArray = new JSONArray(); 836 for (int i = 0; i < attributionConfigsArray.length(); i++) { 837 AttributionConfig attributionConfig = 838 new AttributionConfig.Builder(attributionConfigsArray.getJSONObject(i), mFlags) 839 .build(); 840 validAttributionConfigsArray.put(attributionConfig.serializeAsJson(mFlags)); 841 } 842 return validAttributionConfigsArray.toString(); 843 } 844 isValidXNetworkKeyMapping(JSONObject adTechBitMapping)845 private boolean isValidXNetworkKeyMapping(JSONObject adTechBitMapping) throws JSONException { 846 // TODO: Might need to add logic for keys' and values' lengths. 847 Iterator<String> keys = adTechBitMapping.keys(); 848 while (keys.hasNext()) { 849 String key = keys.next(); 850 String value = adTechBitMapping.optString(key); 851 if (value == null || !value.startsWith("0x")) { 852 return false; 853 } 854 } 855 return true; 856 } 857 getAttributionDestination(Uri destination, AsyncRegistration.RegistrationType registrationType)858 private static Uri getAttributionDestination(Uri destination, 859 AsyncRegistration.RegistrationType registrationType) { 860 return registrationType == AsyncRegistration.RegistrationType.APP_TRIGGER 861 ? BaseUriExtractor.getBaseUri(destination) 862 : destination; 863 } 864 getOdpDelegationManager(Context context, Flags flags)865 private static IOdpDelegationWrapper getOdpDelegationManager(Context context, Flags flags) { 866 if (!SdkLevel.isAtLeastT() || !flags.getMeasurementEnableOdpWebTriggerRegistration()) { 867 return new NoOdpDelegationWrapper(); 868 } 869 870 OnDevicePersonalizationSystemEventManager odpSystemEventManager = null; 871 try { 872 odpSystemEventManager = 873 context.getSystemService(OnDevicePersonalizationSystemEventManager.class); 874 } catch (Exception e) { 875 LoggerFactory.getMeasurementLogger().d(e, "getOdpDelegationManager: Unknown Exception"); 876 ErrorLogUtil.e( 877 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR, 878 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 879 } 880 return (odpSystemEventManager != null) 881 ? new OdpDelegationWrapperImpl(odpSystemEventManager) 882 : new NoOdpDelegationWrapper(); 883 } 884 885 private interface TriggerHeaderContract { 886 String HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER = 887 "Attribution-Reporting-Register-Trigger"; 888 // Header for verbose debug reports. 889 String HEADER_ATTRIBUTION_REPORTING_INFO = "Attribution-Reporting-Info"; 890 String ATTRIBUTION_CONFIG = "attribution_config"; 891 String EVENT_TRIGGER_DATA = "event_trigger_data"; 892 String FILTERS = "filters"; 893 String NOT_FILTERS = "not_filters"; 894 String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data"; 895 String AGGREGATABLE_VALUES = "aggregatable_values"; 896 String AGGREGATABLE_DEDUPLICATION_KEYS = "aggregatable_deduplication_keys"; 897 String DEBUG_KEY = "debug_key"; 898 String DEBUG_REPORTING = "debug_reporting"; 899 String X_NETWORK_KEY_MAPPING = "x_network_key_mapping"; 900 String DEBUG_JOIN_KEY = "debug_join_key"; 901 String DEBUG_AD_ID = "debug_ad_id"; 902 String AGGREGATION_COORDINATOR_ORIGIN = "aggregation_coordinator_origin"; 903 String AGGREGATABLE_SOURCE_REGISTRATION_TIME = "aggregatable_source_registration_time"; 904 String TRIGGER_CONTEXT_ID = "trigger_context_id"; 905 String ATTRIBUTION_SCOPES = "attribution_scopes"; 906 } 907 908 private interface OdpTriggerHeaderContract { 909 String HEADER_ODP_REGISTER_TRIGGER = "Odp-Register-Trigger"; 910 } 911 } 912