Skip to content

Commit

Permalink
Modify Storage Access to use a "per-frame" model (#141)
Browse files Browse the repository at this point in the history
This is an attempt to migrate to a "per-frame" model, as discussed in #122. This is built on top of #138 as a starting point.

The approach is to define a flag that lives on environment, and is set by document.requestStorageAccess and read by document.hasStorageAccess. In order to propagate storage access during self-initiated, same-origin navigations, we also add a flag to the source snapshot params used during create navigation params by fetching, and conditionally copy the sourceDocument's relevant settings object's flag over to the new environment that will be created. This should let us achieve the benefits of the BrowsingContext approach discussed in #122, without having to add and clear state in BrowsingContext.

Co-authored-by: Johann Hofmann <[email protected]>
  • Loading branch information
cfredric and johannhof authored Jan 18, 2023
1 parent c459848 commit 69e3e26
Showing 1 changed file with 75 additions and 84 deletions.
159 changes: 75 additions & 84 deletions storage-access.bs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver
text: unsupported operation; url: dfn-unsupported-operation
text: session; url: dfn-session
text: success; url: dfn-success

spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
type: dfn
text: source snapshot params; url: browsing-the-web.html#source-snapshot-params
text: snapshotting source snapshot params; url: browsing-the-web.html#snapshotting-source-snapshot-params
text: create navigation params by fetching; url: browsing-the-web.html#create-navigation-params-by-fetching
text: set up a window environment settings object; url: nav-history-apis.html#set-up-a-window-environment-settings-object
text: environment
text: DOM manipulation task source; url: webappapis.html#dom-manipulation-task-source

spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
type: dfn
for: response
text: has-cross-origin-redirects; url: #response-has-cross-origin-redirects
</pre>

<pre class=biblio>
Expand Down Expand Up @@ -112,23 +126,14 @@ A {{Document}} is in a <dfn>first-party-site context</dfn> if it is the [=active

A {{Document}} is in a <dfn>third party context</dfn> if it is not in a [=first-party-site context=].

<h3 id="ua-state">User Agent state related to storage access</h3>

A <dfn>storage access map</dfn> is a [=map=] whose keys are [=partitioned storage keys=] and whose values are [=storage access flag sets=].

User Agents maintain a single <dfn>global storage access map</dfn>.
<h3 id="ua-state">Changes to user agent state related to storage access</h3>

ISSUE(privacycg/storage-access#2): What is the lifecycle of the [=global storage access map=]? How long do we remember its contents? Firefox and Safari differ here.
Modify the definition of [=environment=] in the following manner:
1. Add a new member called <dfn for="environment">has storage access</dfn> of type [=boolean=].

ISSUE(privacycg/storage-access#5): When do we age out entries in the [=global storage access map=]? See also [Scope of Storage Access](https://github.com/privacycg/storage-access#scope-of-storage-access).

Each [=agent cluster=] has a <dfn for="agent cluster">storage access map</dfn>.

When an [=agent cluster=] is created, its [=agent cluster/storage access map=] is initialized with a [=map/clone=] of the [=global storage access map=].

To <dfn type="abstract-op">obtain the storage access map</dfn> for a {{Document}} |doc|, run the following steps:

1. Return the [=agent cluster/storage access map=] of |doc|'s [=relevant agent=]'s [=agent cluster=].
Modify the definition of [=source snapshot params=] in the following manner:
1. Add a new member called <dfn for="source snapshot params">has storage access</dfn> of type [=boolean=].
1. Add a new member called <dfn for="source snapshot params">environment id</dfn> of type opaque [=string=].

A <dfn>partitioned storage key</dfn> is a [=tuple=] consisting of a <dfn for="partitioned storage key">top-level site</dfn> (a [=site=]) and an <dfn for="partitioned storage key">embedded origin</dfn> (an [=/origin=]).

Expand All @@ -146,18 +151,6 @@ To <dfn type="abstract-op">generate a partitioned storage key</dfn> for a {{Docu
1. Let |top-level site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=top-level origin=].
1. Return the [=partitioned storage key=] (|top-level site|, |site|).

A <dfn>storage access flag set</dfn> is a set of zero or more of the following flags, which are used to gate access to client-side storage for |embedded origin| when loaded in a [=third party context=] on |top-level site|:

: 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|.

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:

1. If |map|[|key|] [=map/exists|does not exist=], run these steps:
1. Let |flags| be a new [=storage access flag set=].
1. [=map/Set=] |map|[|key|] to |flags|.
1. Return |map|[|key|].

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

<pre class="idl">
Expand All @@ -174,17 +167,14 @@ When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>ha
<!-- https://hg.mozilla.org/mozilla-central/file/tip/dom/base/Document.cpp#l15512 -->

1. Let |p| be [=a new promise=].
1. If |doc| is not [=Document/fully active=], then [=reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|.
1. Let |global| be |doc|'s [=relevant global object=].
1. If |global| is not a [=secure context=], then [=resolve=] |p| with false and return |p|.
1. If |global| is not a [=secure context=], then [=/resolve=] |p| with false and return |p|.
1. If |doc|'s [=Document/browsing context=] is a [=top-level browsing context=], [=/resolve=] |p| with true and return |p|.
1. If the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|. <!-- https://github.com/privacycg/storage-access/issues/40 -->
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. 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. [=Queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p| with |global's| [=environment/has storage access=].
1. Return |p|.

ISSUE: Shouldn't step 8 be [=same site=]?
Expand All @@ -196,69 +186,70 @@ When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>re
<!-- https://hg.mozilla.org/mozilla-central/file/tip/dom/base/Document.cpp#l15629 -->

1. Let |p| be [=a new promise=].
1. If |doc| is not [=Document/fully active=], then [=reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. Let |global| be |doc|'s [=relevant global object=].
1. If |global| is not a [=secure context=], then [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |global| is not a [=secure context=], then [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/browsing context=] is a [=top-level browsing context=], [=/resolve=] and return |p|.
1. If |doc| is not [=allowed to use=] the `"storage-access"` permission, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. <!-- https://github.com/privacycg/storage-access/issues/40 -->
1. If |doc| is not [=allowed to use=] "`storage-access`", [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. <!-- https://github.com/privacycg/storage-access/issues/40 -->
1. If |doc|'s [=Document/origin=] is [=same origin=] with the [=top-level origin=] of |doc|'s [=relevant settings object=], [=/resolve=] and return |p|.
1. If |doc|'s [=active sandboxing flag set=] has its [=sandbox storage access by user activation flag=] set, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} 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, [=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 [=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 [=permissions task source=] given |global| to
1. Set |flag set|'s [=has storage access flag=].
1. If |hasAccess| is true, resolve |p|.
1. Reject |p| with a "{{NotAllowedError}}" {{DOMException}}.
1. If |doc|'s [=active sandboxing flag set=] has its [=sandbox storage access by user activation flag=] set, [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |global|'s [=environment/has storage access=] is true, [=/resolve=] |p| with {{undefined}} and return.
1. Let |has transient activation| be whether |doc|'s {{Window}} object has [=transient activation=].
1. Run the following steps [=in parallel=]:
1. Let |process permission state| be an algorithm that, given a [=permission state=] |state|, runs the following steps:
1. [=Queue a global task=] on the [=permission task source=] given |global| to:
1. If |state| is [=permission/granted=]:
1. Set |global|'s [=environment/has storage access=] to true.
1. [=/Resolve=] |p| with {{undefined}}.
1. Else:
1. [=Consume user activation=] given |global|.
1. [=/Reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}.
1. Let |previous permission state| be the result of [=getting the current permission state=] given "<a permission><code>storage-access</code></a>" and |global|.
1. If |previous permission state| is not [=permission/prompt=]:
1. Run |process permission state| with |previous permission state|.
1. Abort these steps.
1. If |has transient activation| is false:
1. Run |process permission state| with [=permission/denied=].
1. Abort these steps.
1. Let |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
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.
1. If |implicitly granted| is true:
1. Run |process permission state| with [=permission/granted=].
1. Abort these steps.
1. If |implicitly denied| is true:
1. Run |process permission state| with [=permission/denied=].
1. Abort these steps.
1. Let |permissionState| be the result of [=requesting permission to use=] "<a permission><code>storage-access</code></a>".
1. Run |process permission state| with |permissionState|.
1. Return |p|.

NOTE: The intent of this algorithm is to always require user activation before a storage-access permission will be set. Though it is within the means of user agents to set storage-access permissions based on custom heuristics without prior user activation, this specification strongly discourages such behavior, as it could lead to interoperability issues.

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>

Different User Agents have different policies around whether or not [=sites=] may access their [=unpartitioned data=] when they're in a [=third party context=]. User Agents check and/or modify these policies when client-side storage is accessed (see [[#storage]]) as well as when {{Document/hasStorageAccess()}} and {{Document/requestStorageAccess()}} are called.

To <dfn type="abstract-op">determine if a site has storage access</dfn> with [=partitioned storage key=] |key| and {{Document}} |doc|, run these steps:
<h3 id="navigation">Changes to navigation</h3>

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. Return false.
When [=snapshotting source snapshot params=]:
1. Set [=source snapshot params/has storage access=] to |sourceDocument|'s [=source snapshot params/has storage access=].
1. Set [=source snapshot params/environment id=] to |sourceDocument|'s [=relevant settings object=]'s [=environment/id=].

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:
To the [=create navigation params by fetching=] algorithm, insert the following step as step 3:
1. Let |originalURL| be <var ignore>entry</var>'s URL.

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.
1. Let |global| be |doc|'s [=relevant global object=].
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 |doc|'s {{Window}} object has [=transient activation=], [=consume user activation=] with it.
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>
When creating |request|'s [=reserved client=] in [=create navigation params by fetching=]:
1. Set [=reserved client=]'s [=environment/has storage access=] to |sourceSnapshotParams|'s [=source snapshot params/has storage access=] if all of the following hold:
1. |sourceSnapshotParams|'s [=source snapshot params/environment id=] equals <var ignore>navigable</var>'s [=active document=]'s [=relevant settings object=]'s [=environment/id=].
1. |originalURL|'s [=url/origin=] is [=same origin=] with <var ignore>currentURL</var>'s [=url/origin=].
1. |response| is null or |response|'s [=response/has-cross-origin-redirects=] is false.
1. Otherwise, set |request|'s [=reserved client=]'s [=environment/has storage access=] to false.

Before changing the current entry of a session history, run the following steps:
When [=set up a window environment settings object|setting up a window environment settings object=]:
1. Set <var ignore>settings object</var>'s [=environment/has storage access=] to <var ignore>reserved environment</var>'s [=environment/has storage access=].

1. Let |doc| be current entry's {{Document}}.
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|'s [=Document/browsing context=]'s [=top-level browsing context=].
1. Let |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
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=].

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

Expand All @@ -270,9 +261,9 @@ This API only impacts HTTP cookies. A future revision of this API might impact o

<h4 id="cookies">Cookies</h4>

This API is intended to be used with environments and user agent configurations that block access to unpartitioned cookies in a [=third party context=]. At the time of this writing, this concept has not yet been integrated into the [=HTTP-network-or-cache fetch=] and {{Document/cookie}} algorithms. To allow for such an integration, the [=cookie store=] will need to be modified to receive information about the top-level and embedded site of the request (to determine whether to attach cross-site, partitioned, or no cookies) as well as whether the request was made for a document that has storage access, through running the [=determine if a site has storage access=] algorithm that is defined in this specification.
This API is intended to be used with environments and user agent configurations that block access to unpartitioned cookies in a [=third party context=]. At the time of this writing, this concept has not yet been integrated into the [=HTTP-network-or-cache fetch=] and {{Document/cookie}} algorithms. To allow for such an integration, the [=cookie store=] will need to be modified to receive information about the top-level and embedded site of the request (to determine whether to attach cross-site, partitioned, or no cookies) as well as whether the request was made for a document that has storage access, through accessing the [=environment=]'s [=environment/has storage access=] that is defined in this specification.

Once the cookie store allows for receiving information about storage access, we would update [=HTTP-network-or-cache fetch=] and {{Document/cookie}} to run [=determine if a site has storage access=] and pass this information to the [=cookie store=] when retrieving cookies.
Once the cookie store allows for receiving information about storage access, we would update [=HTTP-network-or-cache fetch=] and {{Document/cookie}} to pass the [=environment=]'s [=environment/has storage access=] to the [=cookie store=] when retrieving cookies.

When getting unpartitioned cookies from the [=cookie store=] with storage access, user agents will still follow applicable `SameSite` restrictions (i.e., not attach cookies marked `SameSite=Strict` or `SameSite=Lax` in [=third party contexts=]).

Expand Down

0 comments on commit 69e3e26

Please sign in to comment.