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