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

Use the Permissions API (closes #121, #32) #138

Merged
merged 10 commits into from
Jan 5, 2023
95 changes: 54 additions & 41 deletions storage-access.bs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ urlPrefix: https://fetch.spec.whatwg.org/; spec: Fetch
spec: RFC6265; urlPrefix: https://tools.ietf.org/html/rfc6265
type: dfn
text: cookie store; url: section-5.3
urlPrefix: https://w3c.github.io/permissions/; spec: permissions
text: permissions task source; url: #permissions-task-source; type: dfn
johannhof marked this conversation as resolved.
Show resolved Hide resolved
urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver
type: dfn
text: current browsing context; url: dfn-current-browsing-context
Expand Down Expand Up @@ -148,8 +150,6 @@ A <dfn>storage access flag set</dfn> is a set of zero or more of the following f

: The <dfn for="storage access flag set" id=has-storage-access-flag>has storage access flag</dfn>
:: When set, this flag indicates |embedded origin| has access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|.
johannhof marked this conversation as resolved.
Show resolved Hide resolved
: The <dfn for="storage access flag set" id=was-expressly-denied-storage-access-flag>was expressly denied storage access flag</dfn>
:: When set, this flag indicates that the user expressly denied |embedded origin| access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|.

To <dfn type="abstract-op">obtain a storage access flag set</dfn> for a [=partitioned storage key=] |key| from a [=/storage access map=] |map|, run the following steps:

Expand All @@ -158,10 +158,6 @@ To <dfn type="abstract-op">obtain a storage access flag set</dfn> for a [=partit
1. [=map/Set=] |map|[|key|] to |flags|.
1. Return |map|[|key|].

To <dfn type="abstract-op">save the storage access flag set</dfn> for a [=partitioned storage key=] |key| in a [=/storage access map=] |map|, run the following steps:

1. [=map/Set=] [=global storage access map=][|key|] to |map|[|key|].

<h3 id="the-document-object">Changes to {{Document}}</h3>

<pre class="idl">
Expand All @@ -187,14 +183,8 @@ When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>ha
1. If |doc|'s [=Document/origin=] is [=same origin=] with the [=top-level origin=] of |doc|'s [=relevant settings object=], [=/resolve=] |p| with true and return |p|.
1. Let |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
1. If |key| is failure, [=resolve=] |p| with false and return |p|.
1. Run these steps [=in parallel=]:
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. If |flag set|'s [=was expressly denied storage access flag=] is set, [=queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p| with false, and abort these steps.
1. If |flag set|'s [=has storage access flag=] is set, [=queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p| with true, and abort these steps.
1. Let |hasAccess| be [=a new promise=].
1. [=Determine the storage access policy=] with |key|, |doc| and |hasAccess|.
1. [=Queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p| with the result of |hasAccess|.
1. Let |hasAccess| be the result of running [=determine if a site has storage access=] with |key| and |doc|.
1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p| with |hasAccess|.
1. Return |p|.

ISSUE: Shouldn't step 8 be [=same site=]?
Expand All @@ -220,17 +210,18 @@ When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>re
1. If |key| is failure, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. If |flag set|'s [=was expressly denied storage access flag=] is set, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |flag set|'s [=has storage access flag=] is set, [=/resolve=] and return |p|.
1. Otherwise, run these steps [=in parallel=]:
1. Let |hasAccess| be [=a new promise=].
1. [=Determine the storage access policy=] with |key|, |doc| and |hasAccess|.
1. [=Queue a global task=] on the [=permission task source=] given |global| to
1. [=Queue a global task=] on the [=permissions task source=] given |global| to
1. Set |flag set|'s [=has storage access flag=].
1. Resolve or reject |p| based on the result of |hasAccess|.
1. [=Save the storage access flag set=] for |key| in |map|.
1. If |hasAccess| is true, resolve |p|.
1. Reject |p| with a "{{NotAllowedError}}" {{DOMException}}.
1. Return |p|.

ISSUE(privacycg/storage-access#144): We shouldn't use the permissions task source here.

ISSUE: Shouldn't step 9 be [=same site=]?

<h4 id="ua-policy">User Agent storage access policies</h4>
Expand All @@ -242,36 +233,21 @@ To <dfn type="abstract-op">determine if a site has storage access</dfn> with [=p
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. If |flag set|'s [=has storage access flag=] is set, return true.
1. Let |has storage access| (a [=boolean=]) be the result of running an [=implementation-defined=] set of steps to determine if |key|'s [=partitioned storage key/embedded origin=] has access to its [=unpartitioned data=] on |key|'s [=partitioned storage key/top-level site=].
1. If |has storage access| is true, set |flag set|'s [=has storage access flag=].
1. [=Save the storage access flag set=] for |key| in |map|.
1. Return |has storage access|.
1. Return false.

To <dfn type="abstract-op">determine the storage access policy</dfn> for [=partitioned storage key=] |key| with {{Document}} |doc| and {{Promise}} |p|, run these steps:

1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. Let |implicitly granted| and |implicitly denied| (each a [=boolean=]) be the result of running an [=implementation-defined=] set of steps to determine if |key|'s [=partitioned storage key/embedded origin=]'s request for storage access on |key|'s [=partitioned storage key/top-level site=] should be granted or denied without prompting the user.

Note: These [=implementation-defined=] set of steps might result in |flag set|'s [=has storage access flag=] and [=was expressly denied storage access flag=] changing, since the User Agent could have relevant out-of-band information (e.g. a user preference that changed) that this specification is unaware of.
1. Let |global| be |doc|'s [=relevant global object=].
1. If |implicitly granted| is true, [=queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p|, and return.
1. If |implicitly denied| is true, [=queue a global task=] on the [=permission task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}, and return |p|.
1. Ask the user if they would like to grant |key|'s [=partitioned storage key/embedded origin=] access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |key|'s [=partitioned storage key/top-level site=], and wait for an answer. Let |expressly granted| and |expressly denied| (both [=booleans=]) be the result.

Note: While |expressly granted| and |expressly denied| cannot both be true, they could both be false in User Agents which allow users to dismiss the prompt without choosing to allow or deny the request. (Such a dismissal is interpreted in this algorithm as a denial.)
1. If |expressly granted| is true, run these steps:
1. Unset |flag set|'s [=was expressly denied storage access flag=].
1. [=Save the storage access flag set=] for |key| in |map|.
1. [=Queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p|, and return.
1. If |implicitly granted| is true, [=queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p|, and return.
1. If |implicitly denied| is true, [=queue a global task=] on the [=permissions task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}, and return.
1. Let |permissionState| be the result of [=requesting permission to use=] "<a permission><code>storage-access</code></a>".
1. If |permissionState| is "granted", [=queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p|, and return.
1. Unset |flag set|'s [=has storage access flag=].
1. If |expressly denied| is true, run these steps:
1. If |doc|'s {{Window}} object has [=transient activation=], [=consume user activation=] with it.
1. Set |flag set|'s [=was expressly denied storage access flag=].
1. [=Save the storage access flag set=] for |key| in |map|.
1. [=Queue a global task=] on the [=permission task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}.

ISSUE: [since this is UA-defined, does it make sense to follow-up separately with a user prompt?](https://github.com/privacycg/storage-access/pull/24#discussion_r408784492)
1. If |doc|'s {{Window}} object has [=transient activation=], [=consume user activation=] with it.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be consumed as part of the task in the next step. (Although maybe we should also point to the open issue regarding this.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, let's do this separately from this PR, it's not really related.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you file an issue for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, what I meant was that #141 is moving consumption anyway and that we should do that in that PR. I'll add a note there.

1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}.

<h3 id="navigation">Changes to navigation</h3>

Expand All @@ -283,7 +259,6 @@ Before changing the current entry of a session history, run the following steps:
1. If |key| is failure, abort these steps.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. Unset |flag set|'s [=has storage access flag=].
1. [=Save the storage access flag set=] for |key| in |map|.

ISSUE(privacycg/storage-access#3): What this section should look like ultimately hinges on

Expand Down Expand Up @@ -313,6 +288,44 @@ To the [=parse a sandboxing directive=] algorithm, add the following under step
<li>The [=sandbox storage access by user activation flag=], unless <var ignore>tokens</var> contains the <dfn export attr-value for=iframe/sandbox>allow-storage-access-by-user-activation</dfn> keyword.
</ul>

<h2 id="permissions-integration">Permissions Integration</h2>

The Storage Access API defines a [=powerful feature=] identified by the [=powerful feature/name=] "<dfn export permission><code>storage-access</code></dfn>". It defines the following permission-related algorithms:

<dl>
<dt>[=powerful feature/permission query algorithm=]</dt>
<dd>
To query the "<a permission><code>storage-access</code></a>" permission, given a {{PermissionDescriptor}} |permissionDesc| and a {{PermissionStatus}} |status|:

1. Set |status|'s {{PermissionStatus/state}} to |permissionDesc|'s [=permission state=].
1. If |status|'s {{PermissionStatus/state}} is [=permission/denied=], set |status|'s {{PermissionStatus/state}} to [=permission/prompt=].
johannhof marked this conversation as resolved.
Show resolved Hide resolved

Note: The "denied" permission state is not revealed to avoid exposing the user's decision to developers. This is done to prevent retaliation against the user and repeated prompting to the detriment of the user experience.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's have a follow-up issue on this as we will to some extent expose denied to websites anyway (but they won't know whether it's persisted).

Copy link
Member Author

Choose a reason for hiding this comment

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

You were thinking of #149, right?

</dd>
<dt>[=powerful feature/permission key type=]</dt>
<dd>
A [=permission key=] of the "<a permission><code>storage-access</code></a>" feature is a [=tuple=] consisting of a [=site=] <dfn for="permission key">top-level</dfn> and an [=/origin=] <dfn for="permission key">requester</dfn>.

ISSUE(privacycg/storage-access#147): Note that this will likely change to a (site, site) keying.
</dd>
<dt>[=powerful feature/permission key generation algorithm=]</dt>
<dd>
To generate a new [=permission key=] for the "<a permission><code>storage-access</code></a>" feature, given an [=environment settings object=] |settings|, run the following steps:

1. Let |topLevelSite| be |settings|' [=top-level site=].
1. Let |embeddedOrigin| be |settings|' [=environment settings object/origin=].
1. Return (|topLevelSite|, |embeddedOrigin|).
</dd>
<dt>[=powerful feature/permission key comparison algorithm=]</dt>
<dd>
To compare the [=permission keys=] |key1| and |key2| for the "<a permission><code>storage-access</code></a>" feature, run the following steps:

1. If |key1|'s [=permission key/top-level=] is not [=same site=] with |key2|'s [=permission key/top-level=], return false.
1. If |key1|'s [=permission key/requester=] is not [=same origin=] with |key2|'s [=permission key/requester=], return false.
1. Return true.
</dd>
</dl>

<h2 id="permissions-policy-integration">Permissions Policy Integration</h2>

The Storage Access API defines a [=policy-controlled feature=] identified by the string `"storage-access"`. Its [=default allowlist=] is `"*"`.
Expand Down