Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data Deletion Request Handing spec #6

Merged
merged 11 commits into from May 26, 2020
328 changes: 328 additions & 0 deletions CCPA/Data Deletion Request Handling.md
@@ -0,0 +1,328 @@
# Data Deletion Request Handling

## Summary

This specification provides a way for a publisher to communicate to downstream parties about a consumer's request to delete their data.

In particular, Section 1798.105(c) of the California Consumer Protection Act (CCPA) states that "[a] business that receives a verifiable consumer request from a consumer to delete the consumer's personal information [shall] . . direct any service providers to delete the consumer's personal information from their records."

In response to this directive in the CCPA, this specification offers the technical means for publishers to communicate deletion requests to their service providers. All service providers covered by IAB Privacy LLC's Limited Service Provider Agreement (LSPA) and any other providers who receive the signal for deletion requests are then enabled to comply and delete the specified consumer data.

While this specification provides the technical means for compliance in cases where CCPA applies, implementations may exist to support deletion requests outside CCPA governance.


### Relevant Documents

[Limited Service Provider Agreement](https://www.iabprivacy.com/lspa-2019-12.pdf) (12-2-19 version)


## How it Works

Any ad tech company (vendor) supporting the US Privacy framework, exposes a JavaScript resource for each service they offer. The vendor hosts a URL that provides this resource and also serves as a unique identifier for the resource. The URL is made public to a maintained LSPA signatories list and to non-signatories via the vendor's method of choice.

Publishers working with any services provided by these vendors consume the exposed JavaScript and embed it on their properties where consumers can request deletion of their personal data. If data deletion is requested, all vendor javascript on the publisher property is notified. Vendors then respond by deleting the relevant personal data and signaling any affected vendors downstream.

alextcone marked this conversation as resolved.
Show resolved Hide resolved

### Multiple Services
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the blurb above in the CCPA Regulation:

direct any service providers to delete the consumer's personal information from their records

The relationship is consumer to service provider not consumer to service. So, therefore, no matter how many services a Vendor provides, there is only one request to be made to that Vendor and therefore no need to support multiple services. There is nothing prohibiting a Vendor from doing this either, but it shouldn't be specified in this document.


For vendors that provide multiple services, a URL is provided for each service. A publisher may use any of those services but not necessarily all of them. When a consumer requests data deletion, only the scripts provided on the page will trigger the delete request back to the vendor. Vendors can then delete the personal data, in accordance with their practices, for each service provided on the publisher property where the request originated.


### Deletion

The technical solution detailed in this specification provides the means to signal consumer requests for data deletion. Companies supporting the US Privacy Framework (i.e., service providers) will respond to the signals by deleting the consumer's relevant personal data to the extent required by CCPA. The process for deletion depends on the company's technology and operational practices in place. _How_ a vendor deletes a consumer's personal data is out of scope for this specification.


### Non-web Environments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"In-App" is correct term here. "In-App" was created specifically for this use case. I understand it has a Mobile-only feel but that's only because Mobile is the biggest use case. If a clarifying statement is needed then add that.

My problem with Non-web is that it then proceeds to tell us to use a web page in the non-web environment. It sounds like there is no Internet connection or something.


If the consumer initiates a data deletion request from a non-web environment, the request can be propagated through the same javascript resource described above by directing the user to a publisher webpage to complete the deletion request. The request can be associated to the device from where it originated using defined parameters.


## Deletion Signaling

Deletion signaling uses two commands: `registerDeletion` and `performDeletion`. The first command, `registerDeletion`, is executed by the vendor script when the publisher loads it on a given page or property. This registers the vendor script to receive the signal sent by the `performDeletion` call. The second command, `performDeletion`, is executed by the publisher if a consumer requests data deletion and signals the vendor that a consumer has requested that their personal data be deleted.


### Sample Workflow
1. Publisher loads scripts provided by vendors.
2. The vendors' scripts execute `registerDeletion` at load.
3. When a data deletion request is made by the user on the page, the publisher executes `performDeletion`, which executes all vendors' deletion scripts that have been registered with` registerDeletion.`
4. Vendors perform deletion asynchronously, in accordance with their technology and operational practices.


### How should publishers load vendor scripts?

Vendors need access to the publisher domain to get a user identifier to associate with a delete request. In order for vendors to do so, the vendor scripts described in this specification must be loaded _directly _on the publisher domain normally used for collecting data and serving ads and not on a separate or generic domain. For safety, the vendor scripts can be loaded in an iframe, provided the following conditions are met:
* The iframe is hosted on the publisher domain
* If the iframe sandbox parameter is provided, the following restrictions are lifted:
* `allow-scripts`
* `allow-storage-access-by-user-activation`


## registerDeletion

This command registers a vendor-specific callback function at the API. When the publisher loads the vendor deletion script, those scripts must execute `registerDeletion`.

This command is an update to the existing `__uspapi` function with the following syntax:

```
__usapi("registerDeletion", version, performDeletionFunction)
```

Upon execution, the vendor script is registered to receive the signal sent by the call to `performDeletion`.


## performDeletion

The publisher, or its CMP where applicable, calls this command based on some user delete action to initiate the deletion process. The command communicates to vendors that a specified user has requested that their personal data be deleted by calling the `performDeletionFunction` registered during the call to `registerDeletion`.

This command is an update to the existing `__uspapi` function with the following syntax:

```
__usapi("performDeletion", version, null, identifiers)
```

The callback parameter of the `__uspapi` is not used in this case and can remain null. The `identifiers` parameter is only required when handling non-web deletion requests and is further explained in the following section.


### Identifying Data to Delete in Non-web Environments

When operating in a non-web environment, data deletion requests are handled by sending the user to a web page where they can complete the request to have their data deleted. Vendors need certain information to correctly identify the data to delete: the platform name, the unique app identifier used in the app store, and the device identifier for that platform / store.

The `performDeletion` command includes a parameter for identifiers. Using the `identifiers` parameter, publishers can pass multiple items, each with the required fields: `platform`, `app_identifier`, and `user_identifier`. These details should be passed from the app context to the delete webpage and then along with the request from the page. Below are a few examples of that information:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is ever possible to have more than one platform, app_identifier, or user_identifier in a single app.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may need to pass the deletion for multiple apps at the same time?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Facens this software only runs in one App at a time


```
identifiers {
...
"platform": "ios-app-store",
"app_identifier": "01234567891446075923",
"user_identifier": "AEA12347583AACD-A123667-A418AABC-AB123806-1242AEAACB12AB1234548606"
}
...
{
"platform": "android-play-store",
"app_identifier": "com.acmeinc.acmeapp",
"user_identifier": "aaaacdda123802ae-11fb191c-1247abad-12340794ad123394ac123912"
...
}
```

```
Note: For purposes of this explanation, a "bundle id" is used for (iOS) and a "package" for (Android).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for which property?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app_identifier I believe @chrispaterson

```

### Common Platform / Stores identifiers

These identifiers can be used for the "platform" field where applicable. Otherwise, the platform field can include an arbitrary value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can platform contain an arbitrary value? If a Vendor is meant to interpret this information to be able to identify a user, this can not be arbitrary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've decided to keep it open in order to support additional platforms that aren't listed (knowing that we can't possibly list them all)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Facens if it's arbitrary it's not usable because this information enables a Vendor script to interpret the other two values. For example: If I'm a Vendor and I receive a "platform" value of "foo-bar-23" and it's not a platform I recognize, what do I do? It won't match any user identifier information I have on record and I can not delete the relevant data.


<table>
<tr>
<td>Android Google Play Store
</td>
<td>android-play-store
</td>
</tr>
<tr>
<td>Android Amazon Store
</td>
<td>android-amazon-store
</td>
</tr>
<tr>
<td>iOS App Store
</td>
<td>ios-app-store
</td>
</tr>
<tr>
<td>Samsung App Store
</td>
<td>samsung-app-store
</td>
</tr>
<tr>
<td>Huawei app store
</td>
<td>huawei-app-store
</td>
</tr>
<tr>
<td>Sony apps
</td>
<td>sony-app-store
</td>
</tr>
<tr>
<td>LG smartworld
</td>
<td>lg-app-store
</td>
</tr>
</table>


## Example Implementation

Publisher site setup:

```
<html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole section is WAY too much. There is already a specification that defines how to set up the __usapiLocator frame and stub. There is too much repeated information and it's too easy to get lost in all of this.

<head>
<script>
//API provided by the publisher or CMP:
function uspapi_addFrame()
{
if(!window.frames['__uspapiLocator'])
{
if(document.body)
{
var i = document.createElement('iframe');
i.style.cssText = 'display:none';
i.name = '__uspapiLocator';
document.body.appendChild(i);
}
else
{
window.setTimeout('uspapi_addFrame()', 10);
}
}
}

function uspapi_stub()
{
var b = arguments;
__uspapi.a = __uspapi.a || [];
if(!b.length)
{
return __uspapi.a;
}
else
{
__uspapi.a.push([].slice.apply(b));
}
}

function uspapi_msghandler(event)
{
var msgIsString = typeof event.data === 'string';
try
{
var json = msgIsString ? JSON.parse(event.data) : event.data;
}
catch(e)
{
var json = null;
}
if(typeof (json) === 'object' && json !== null && '__uspapiCall' in json)
{
var i = json.__uspapiCall;
window.__uspapi(i.command, i.version, function (retValue, success)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need the extra optional parameter ('identifiers') used in non-web contexts as a placeholder?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, probably

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you show exactly where that would go?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already there I think. I may have missed it in my original comment or it may have been added (not honestly sure which). It's on line 229 below.

{
var returnMsg = {
'__uspapiReturn': {
'returnValue': retValue,
'success' : success,
'callId' : i.callId
}
};
event.source.postMessage(msgIsString ? JSON.stringify(returnMsg) : returnMsg, '*');
}, i.parameter);
}
}

uspapi_addFrame();
if(!('__uspapi' in window) ||
(typeof (window['__uspapi']) !== 'function' &&
typeof (window['__uspapi']) !== 'object' &&
(typeof (window['__uspapi']) === 'undefined' ||
window['__uspapi'] !== null)))
{
window['__uspapi'] = uspapi_stub;
window['__uspapi'].msgHandler = uspapi_msghandler;
if(window.addEventListener)
{
window.addEventListener('message', uspapi_msghandler, false);
}
else
{
window.attachEvent('onmessage', uspapi_msghandler);
}
}
</script>
<!-- include the vendor deletion scripts into the page: -->
<script src="https://vendorx.com/ccpa-delete-function.js"></script>
</head>
<body>
<button id="ccpa-delete">Delete my personal data</button>
<script>
document.getElementById('ccpa-delete').addEventListener('click', function ()
{
__uspapi('performDeletion', 1, null);
}, false);
</script>
</body>
</html>
```

Vendor script (at `https://vendorx.com/ccpa-delete-function.js`)

```
// find the __uspapi frame
var f = window;
var cmpFrame;
var cmpCallbacks = {};
while (!cmpFrame) {
try {
if (f.frames['__uspapiLocator']) {
cmpFrame = f;
}
} catch (e) {}
if (f === window.top) {
break;
}
f = f.parent;
}

/* Set up a __uspapi function to do the postMessage and
stash the callback.
This function behaves (from the caller's perspective)
identically to the in-frame __uspapi call */
window.__uspapi = function(cmd, ver, callback, param) {
alextcone marked this conversation as resolved.
Show resolved Hide resolved
if (!cmpFrame) {
callback({
msg: '__uspapi not found'
}, false);
return;
}

var callId = Math.random() + '';
var msg = {
__uspapiCall: {
command: cmd,
parameter: param,
version: ver,
callId: callId
}
};
cmpCallbacks[callId] = callback;
cmpFrame.postMessage(msg, '*');
};

window.addEventListener('message', function(event) {
var json = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
if (json.__uspapiReturn) {
var i = json.__uspapiReturn;
cmpCallbacks[i.callId](i.returnValue, i.success);
delete cmpCallbacks[i.callId];
}
}, false);


function vendorXDeletion() {
//... do some deletion work …
return;
}

__uspapi('registerDeletion', 1, vendorXDeletion);

```
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -12,6 +12,7 @@ More information about the Framework is available at https://iab.com/guidelines/
- [US Privacy String](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md)
- [USP API](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/USP%20API.md)
- [OpenRTB Extension for US Privacy](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/OpenRTB%20Extension%20for%20USPrivacy.md)
- [Data Deletion Request Handling](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/Data%20Deletion%20Request%20Handling.md)
- [CCPA reference implementation](https://github.com/InteractiveAdvertisingBureau/CCPA-reference-code)


Expand Down