patcg / ppa-api

Privacy-Preserving Attribution
https://patcg.github.io/ppa-api/
1 stars 3 forks source link

Partial HTTP API for delegation #47

Open martinthomson opened 2 weeks ago

martinthomson commented 2 weeks ago

This is for #31 and #32/#38, in part. It's the option where the delegation occurs using JS, but the HTTP API is used for the delegated piece.

Top-level (or delegate) calls:

let handle = attribution.delegateConversion({
  epsilon: 1000,
  logic: "last-touch",
  intermediarySites: ["example.com", "bar.example"],
  maxValue: 100,
  filterData: 7,
});

The top-level site passes handle to other frames.

The intermediary site would call whatever API it prefers like fbq('track', handle).

let pixel = document.createElement('img');
pixel.style.display = "none";
pixel.src = `https://example.com/track?delegation=${handle.id}`;
// the src might include other information from the handle,
// which can reflect the attributes passed to the call above
document.body.appendChild(pixel);

The browser then loads the pixel.

GET /track?delegation=<delegation-ID>
Host: example.com

Note: The delegation ID should be hard to guess, so maybe 120 bits of entropy in a string (UUID is awful, but that’s a fine basis for this). That ID would only be valid for a short while, which could be slightly longer than the page lifetime to allow for conversions that are followed by navigation or windows being closed. That allows us to avoid permissions checks and whatnot: knowing the delegation ID is proof that you are authorized to generate a conversion report.

The origin of the site that receives the delegation would need to match one of the listed intermediary sites.

Option 1: Redirect-only

This option might be more natural to some, but it comes with drawbacks. It uses redirects and custom header fields to perform the attribution.

The response includes instructions from the server. This would apply to any response that it gets with these headers, but of course, the site won't know how to identify the delegation without being told the ID.

302 Whatever
Location: https://example.com/save-conversion
Some-Custom-Header: delegation=<delegation-ID>,\
   aggregator=agg.example,histogramSize=20

The browser then follows the redirect.

GET /save-conversion HTTP/9000
Host: example.com
Other-Custom-Header: :MASSIVEBINARYBLOB_USING_BASE_64:

The server then needs to produce an image response.

200 OK
Content-Type: image/gif

IMAGE DATA HERE DOESN'T MATTER BECAUSE IT ISN'T VISIBLE

Option 2: Use link relations:

This is a little more efficient. The response points to where conversion reports should be sent.

200 OK
Content-Type: image/gif
Link: <https://example.com/save-conversion>;rel=attribution; \
  delegation=<delegation-ID>;aggregator=agg.example;histogramSize=20

PIXEL DATA

The browser then loads that resource, posting the conversion report there according to its understanding of the link relation.

POST /save-conversion HTTP/9000
Host: example.com
Content-Type: application/conversion-report
Content-Length: ...

MASSIVEBINARYBLOB_DOES_NOT_NEED_TO_BE_BASE_64_ENCODED

The server response can be empty then.

200 OK
Content-Length: 0

This are essentially the same as the pure redirect, but it avoids the inefficiency of a base64 conversion for the report.

The one obvious drawback is that it doesn’t guarantee that when the browser says the image is loaded, the report has been submitted. That's something that we could integrate into the imaging loading algorithm if necessary.

martinthomson commented 2 weeks ago

I meant to say...

This shape should work for the non-delegated version of the API.