From 5c28dd2234fb9812fafa28d9ef58ad5fdcb8c30d Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 28 Feb 2022 15:40:05 -0500 Subject: [PATCH 1/9] First draft of forward-declared API --- storage-access.bs | 75 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index e864b8e..69049b8 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -74,9 +74,9 @@ urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver This section is non-normative. -User Agents sometimes prevent content inside certain <{iframe}>s from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. +User Agents sometimes prevent third-party content from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. -The Storage Access API enables content inside <{iframe}>s to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]] +The Storage Access API enables third parties to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]] @@ -98,6 +98,18 @@ Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to det +This specification also defines a method for a [=site=] to request access to its [=unpartitioned data=] when later loaded in a [=third party context=] on another specified [=site=] ({{Document/requestStorageAccessUnderSite()}}), and a method for a [=site=] to allow such requests to succeed ({{Document/allowStorageAccessRequestOnSite()}}). + +
+ +Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and opens a new [=window=] for `https://identity.example`. + +In the new [=window=], Alex selects the account to log in with. In response, `https://identity.example` sets a [=cookie=], which by virtue of its [=first party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the `https://identity.example` [=window=] closes itself, returning Alex to `https://site.example`. + +When Alex returns to `https://site.example`, the {{Promise}} returned by `{{Document/allowStorageAccessRequestOnSite()}}` has resolved or will resolve shortly. At this point, requests to `https://idnetity.example` will bear the [=cookie=] set in the previous [=window=]. + +
+ Unpartitioned data is client-side storage that would be available to a [=site=] were it loaded in a [=first-party-site context=]. A {{Document}} is in a first-party-site context if it is the [=active document=] of a [=top-level browsing context=]. Otherwise, it is in a [=first-party-site context=] if it is an [=active document=] and the [=environment settings object/origin=] and [=top-level origin=] of its [=relevant settings object=] are [=same site=] with one another. @@ -144,6 +156,8 @@ A storage access flag set is a set of zero or more of the following f :: When set, this flag indicates |embedded site| has access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. : The was expressly denied storage access flag :: When set, this flag indicates that the user expressly denied |embedded site| access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. +: The has allow request storage access flag +:: When set, this flag indicates |top-level site| is allowing |embedded site| to request access to its [=unpartitioned storage=] on |top-level site| from the |embedded site| [=first party context=]. To obtain a storage access flag set for a [=partitioned storage key=] |key| from a [=/storage access map=] |map|, run the following steps: @@ -162,6 +176,8 @@ To save the storage access flag set for a [=partit partial interface Document { Promise<boolean> hasStorageAccess(); Promise<undefined> requestStorageAccess(); + Promise<undefined> allowRequestStorageAccessOnSite(DOMString serializedSite); + Promise<undefined> requestStorageAccessUnderSite(DOMString serializedSite); }; @@ -223,6 +239,61 @@ When invoked on {{Document}} |doc|, the re ISSUE: Shouldn't step 3.7 be [=same site=]? +When invoked on {{Document}} |doc|, the allowRequestStorageAccessOnSite(DOMString serializedSite) method must run these steps: + +1. Let |p| be [=a new promise=]. +1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. +1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. +1. If |serializedEmbeddingSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. Let |settings| be |doc|'s [=relevant settings object=]. +1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. +1. Let |thirdPartySite| be a [=site=] that serializes to |serializedSite|. +1. Let |key| be the [=partitioned storage key=] (|site|, |thirdPartySite|). +1. Let |global| be |doc|'s [=relevant global object=]. +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. Set |flag set|’s [=has allow request storage access flag=]. +1. [=Save the storage access flag set=] for |key| in |map|. +1. Wait for a call to [{{Document/requestStorageAccessUnderSite()}}] on a {{Document}} with [=site=] |thirdPartySite|. +1. Unset |flag set|’s [=has allow request storage access flag=]. +1. [=Resolve=] and return |p|. + +ISSUE: Vagueness around waiting +ISSUE: Deserialization of site is not well defined + +When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString serializedSite) method must run these steps: + +1. Let |p| be [=a new promise=]. +1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. +1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. +1. If |serializedEmbeddingSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. Let |settings| be |doc|'s [=relevant settings object=]. +1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. +1. Let |firstPartySite| be a [=site=] that serializes to |serializedSite|. +1. Let |key| be the [=partitioned storage key=] (|firstPartySite|, |site|). +1. Let |global| be |doc|'s [=relevant global object=]. +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, + 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. + 1. [=resolve=] |p| + 1. return |p|. +1. If |flag set|’s [=has storage access flag=] is set, + 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. + 1. [=resolve=] |p| + 1. 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. Set |flag set|'s [=has storage access flag=]. + 1. Resolve |p|. + 1. [=Save the storage access flag set=] for |key| in |map|. +1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. +1. Return |p|. + + +

User Agent storage access policies

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. From b3f2af8b6b8d4c1d8bfa1dddc577d41899218ffe Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 28 Feb 2022 15:49:49 -0500 Subject: [PATCH 2/9] Cleaning up some bikeshed link errors --- storage-access.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index 69049b8..c9f587f 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -104,9 +104,9 @@ This specification also defines a method for a [=site=] to request access to its Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and opens a new [=window=] for `https://identity.example`. -In the new [=window=], Alex selects the account to log in with. In response, `https://identity.example` sets a [=cookie=], which by virtue of its [=first party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the `https://identity.example` [=window=] closes itself, returning Alex to `https://site.example`. +In the new [=window=], Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the `https://identity.example` [=window=] closes itself, returning Alex to `https://site.example`. -When Alex returns to `https://site.example`, the {{Promise}} returned by `{{Document/allowStorageAccessRequestOnSite()}}` has resolved or will resolve shortly. At this point, requests to `https://idnetity.example` will bear the [=cookie=] set in the previous [=window=]. +When Alex returns to `https://site.example`, the {{Promise}} returned by `{{Document/allowStorageAccessRequestOnSite()}}` has resolved or will resolve shortly. At this point, requests to `https://idnetity.example` will bear the cookie set in the previous [=window=]. @@ -156,8 +156,8 @@ A storage access flag set is a set of zero or more of the following f :: When set, this flag indicates |embedded site| has access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. : The was expressly denied storage access flag :: When set, this flag indicates that the user expressly denied |embedded site| access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. -: The has allow request storage access flag -:: When set, this flag indicates |top-level site| is allowing |embedded site| to request access to its [=unpartitioned storage=] on |top-level site| from the |embedded site| [=first party context=]. +: The has allow request storage access flag +:: When set, this flag indicates |top-level site| is allowing |embedded site| to request access to its [=unpartitioned data=] on |top-level site| from the |embedded site| [=first-party-site context=]. To obtain a storage access flag set for a [=partitioned storage key=] |key| from a [=/storage access map=] |map|, run the following steps: @@ -275,7 +275,7 @@ When invoked on {{Document}} |doc|, the re 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, - 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. + 1. Notify all waiting tasks of [{{Document/allowRequestStorageAccessOnSite()}}] with argument corresponding |doc|'s [=site=]. 1. [=resolve=] |p| 1. return |p|. 1. If |flag set|’s [=has storage access flag=] is set, From 06832e0dfb0b23eed5b683fc1d43d1e870721558 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 28 Feb 2022 15:52:39 -0500 Subject: [PATCH 3/9] Fix naming conventions --- storage-access.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index c9f587f..399b5fb 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -176,7 +176,7 @@ To save the storage access flag set for a [=partit partial interface Document { Promise<boolean> hasStorageAccess(); Promise<undefined> requestStorageAccess(); - Promise<undefined> allowRequestStorageAccessOnSite(DOMString serializedSite); + Promise<undefined> allowStorageAccessRequestOnSite(DOMString serializedSite); Promise<undefined> requestStorageAccessUnderSite(DOMString serializedSite); }; @@ -254,7 +254,7 @@ When invoked on {{Document}} |doc|, the al 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. Set |flag set|’s [=has allow request storage access flag=]. 1. [=Save the storage access flag set=] for |key| in |map|. -1. Wait for a call to [{{Document/requestStorageAccessUnderSite()}}] on a {{Document}} with [=site=] |thirdPartySite|. +1. Wait for a call to {{Document/requestStorageAccessUnderSite()}} on a {{Document}} with [=site=] |thirdPartySite|. 1. Unset |flag set|’s [=has allow request storage access flag=]. 1. [=Resolve=] and return |p|. @@ -275,7 +275,7 @@ When invoked on {{Document}} |doc|, the re 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, - 1. Notify all waiting tasks of [{{Document/allowRequestStorageAccessOnSite()}}] with argument corresponding |doc|'s [=site=]. + 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. 1. [=resolve=] |p| 1. return |p|. 1. If |flag set|’s [=has storage access flag=] is set, From 99399da4abc85c123bda1fb692eb1a35436e982a Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 28 Feb 2022 15:54:08 -0500 Subject: [PATCH 4/9] Typo --- storage-access.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage-access.bs b/storage-access.bs index 399b5fb..4a34999 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -156,7 +156,7 @@ A storage access flag set is a set of zero or more of the following f :: When set, this flag indicates |embedded site| has access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. : The was expressly denied storage access flag :: When set, this flag indicates that the user expressly denied |embedded site| access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|. -: The has allow request storage access flag +: The has allow request storage access flag :: When set, this flag indicates |top-level site| is allowing |embedded site| to request access to its [=unpartitioned data=] on |top-level site| from the |embedded site| [=first-party-site context=]. To obtain a storage access flag set for a [=partitioned storage key=] |key| from a [=/storage access map=] |map|, run the following steps: From c6021274becf9d9fbba6d147a4602ae33953a8bc Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 11 Apr 2022 07:52:47 -0400 Subject: [PATCH 5/9] Resolve issue of allowRequestStorageAccessOnSite promise resolution brought up by @jasonnutter & @johannhof Also fix some typos --- storage-access.bs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index 4a34999..158bd5c 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -98,15 +98,15 @@ Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to det -This specification also defines a method for a [=site=] to request access to its [=unpartitioned data=] when later loaded in a [=third party context=] on another specified [=site=] ({{Document/requestStorageAccessUnderSite()}}), and a method for a [=site=] to allow such requests to succeed ({{Document/allowStorageAccessRequestOnSite()}}). +This specification also defines a method for a site to request access to its [=unpartitioned data=] when later loaded in a [=third party context=] on another specified site ({{Document/requestStorageAccessUnderSite()}}), and a method for a site to allow such requests to succeed ({{Document/allowStorageAccessRequestOnSite()}}).
-Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and opens a new [=window=] for `https://identity.example`. +Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and opens a new [=window=] for `https://identity.example` when the returned promise resolves. In the new [=window=], Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the `https://identity.example` [=window=] closes itself, returning Alex to `https://site.example`. -When Alex returns to `https://site.example`, the {{Promise}} returned by `{{Document/allowStorageAccessRequestOnSite()}}` has resolved or will resolve shortly. At this point, requests to `https://idnetity.example` will bear the cookie set in the previous [=window=]. +When Alex returns to `https://site.example` requests to `https://identity.example` will bear the cookie set in the previous [=window=].
@@ -244,7 +244,7 @@ When invoked on {{Document}} |doc|, the al 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. -1. If |serializedEmbeddingSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. If |serializedSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. 1. Let |thirdPartySite| be a [=site=] that serializes to |serializedSite|. @@ -254,11 +254,9 @@ When invoked on {{Document}} |doc|, the al 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. Set |flag set|’s [=has allow request storage access flag=]. 1. [=Save the storage access flag set=] for |key| in |map|. -1. Wait for a call to {{Document/requestStorageAccessUnderSite()}} on a {{Document}} with [=site=] |thirdPartySite|. 1. Unset |flag set|’s [=has allow request storage access flag=]. 1. [=Resolve=] and return |p|. -ISSUE: Vagueness around waiting ISSUE: Deserialization of site is not well defined When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString serializedSite) method must run these steps: @@ -266,7 +264,7 @@ When invoked on {{Document}} |doc|, the re 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. -1. If |serializedEmbeddingSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. If |serializedSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. 1. Let |firstPartySite| be a [=site=] that serializes to |serializedSite|. @@ -274,12 +272,10 @@ When invoked on {{Document}} |doc|, the re 1. Let |global| be |doc|'s [=relevant global object=]. 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, - 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. +1. If |flag set|’s [=was expressly denied storage access flag=] is set, 1. [=resolve=] |p| 1. return |p|. -1. If |flag set|’s [=has storage access flag=] is set, - 1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. +1. If |flag set|’s [=has storage access flag=] is set, 1. [=resolve=] |p| 1. return |p|. 1. Otherwise, run these steps [=in parallel=]: @@ -289,7 +285,6 @@ When invoked on {{Document}} |doc|, the re 1. Set |flag set|'s [=has storage access flag=]. 1. Resolve |p|. 1. [=Save the storage access flag set=] for |key| in |map|. -1. Notify all waiting tasks of {{Document/allowRequestStorageAccessOnSite()}} with argument corresponding |doc|'s [=site=]. 1. Return |p|. From 448278d9ca8a488c87ed1045882581ae1ce0efd3 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Thu, 19 May 2022 12:13:05 -0400 Subject: [PATCH 6/9] Removing language change --- storage-access.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage-access.bs b/storage-access.bs index 158bd5c..fb5ee33 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -74,7 +74,7 @@ urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver This section is non-normative. -User Agents sometimes prevent third-party content from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. +User Agents sometimes prevent content inside certain <{iframe}>s from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. The Storage Access API enables third parties to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]] From 2152510be88e82a831c7cf905de7f42b2ebcd840 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Fri, 10 Jun 2022 14:04:33 -0400 Subject: [PATCH 7/9] Update language to avoid site agency and revise algorithms --- storage-access.bs | 57 +++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index fb5ee33..5037150 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -74,9 +74,9 @@ urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver This section is non-normative. -User Agents sometimes prevent content inside certain <{iframe}>s from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. +User Agents sometimes prevent content inside certain <{iframe}>s or requests from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage. -The Storage Access API enables third parties to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]] +The Storage Access API enables Documents to request and be granted access to their unpartitioned client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]] @@ -98,13 +98,14 @@ Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to det -This specification also defines a method for a site to request access to its [=unpartitioned data=] when later loaded in a [=third party context=] on another specified site ({{Document/requestStorageAccessUnderSite()}}), and a method for a site to allow such requests to succeed ({{Document/allowStorageAccessRequestOnSite()}}). +This specification defines a method for a {{Document}} to request access to its [=unpartitioned data=] when later used in a [=third party context=] whose active Document's origin is same-site with a specified site ({{Document/requestStorageAccessUnderSite()}}). +This specification additionally defines a method for a Document to allow such requests to succeed with arguments that are same-site to its origin in a [=browsing context=] whose active Document's origin is same-site with a specified site ({{Document/allowStorageAccessRequestOnSite()}}).
-Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and opens a new [=window=] for `https://identity.example` when the returned promise resolves. +Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and navigates to `https://identity.example` when the returned promise resolves. -In the new [=window=], Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the `https://identity.example` [=window=] closes itself, returning Alex to `https://site.example`. +Once navigated, Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the [=browsing context=] navigates, returning Alex to `https://site.example`. When Alex returns to `https://site.example` requests to `https://identity.example` will bear the cookie set in the previous [=window=]. @@ -176,8 +177,8 @@ To save the storage access flag set for a [=partit partial interface Document { Promise<boolean> hasStorageAccess(); Promise<undefined> requestStorageAccess(); - Promise<undefined> allowStorageAccessRequestOnSite(DOMString serializedSite); - Promise<undefined> requestStorageAccessUnderSite(DOMString serializedSite); + Promise<undefined> allowStorageAccessRequestOnSite(DOMString embeddedURI); + Promise<undefined> requestStorageAccessUnderSite(DOMString topLevelURI); }; @@ -239,45 +240,57 @@ When invoked on {{Document}} |doc|, the re ISSUE: Shouldn't step 3.7 be [=same site=]? -When invoked on {{Document}} |doc|, the allowRequestStorageAccessOnSite(DOMString serializedSite) method must run these steps: +When invoked on {{Document}} |doc|, the allowStorageAccessRequestOnSite(DOMString embeddedURI) method must run these steps: 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. +1. Let |parsedURI| be the result of parsing |embeddedURI|. +1. If |parsedURI| is failure, [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. -1. If |serializedSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. +1. Let |parsedOrigin| be the result of obtaining the origin of |parsedURI|. +1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. -1. Let |thirdPartySite| be a [=site=] that serializes to |serializedSite|. -1. Let |key| be the [=partitioned storage key=] (|site|, |thirdPartySite|). +1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |parsedOrigin|. +1. Let |key| be the [=partitioned storage key=] (|site|, |embeddedSite|). 1. Let |global| be |doc|'s [=relevant global object=]. -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. Set |flag set|’s [=has allow request storage access flag=]. -1. [=Save the storage access flag set=] for |key| in |map|. -1. Unset |flag set|’s [=has allow request storage access flag=]. -1. [=Resolve=] 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. Set |flag set|’s [=has allow request storage access flag=]. + 1. [=Save the storage access flag set=] for |key| in |map|. + 1. [=Resolve=] p. +1. Return |p|. ISSUE: Deserialization of site is not well defined -When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString serializedSite) method must run these steps: +When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString topLevelURI) method must run these steps: 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. +1. Let |parsedURI| be the result of parsing |embeddedURI|. +1. If |parsedURI| is failure, [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. -1. If |serializedSite| is |"null"| or not a valid [=serialization of a site=], [=reject=] and return |p|. +1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. +1. Let |parsedOrigin| be the result of obtaining the origin of |parsedURI|. +1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. -1. Let |firstPartySite| be a [=site=] that serializes to |serializedSite|. -1. Let |key| be the [=partitioned storage key=] (|firstPartySite|, |site|). +1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from |parsedOrigin|. +1. Let |key| be the [=partitioned storage key=] (|topLevelSite|, |site|). 1. Let |global| be |doc|'s [=relevant global object=]. 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, - 1. [=resolve=] |p| + 1. [=reject=] |p| 1. return |p|. 1. If |flag set|’s [=has storage access flag=] is set, 1. [=resolve=] |p| 1. return |p|. +1. If the |flag set|'s [=has allow request storage access flag=] is not set, + 1. [=reject=] |p| + 1. 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|. From a98585eab4275a83dfe58d0c654373819d48fa09 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Fri, 10 Jun 2022 14:40:20 -0400 Subject: [PATCH 8/9] Fix bikeshed errors --- storage-access.bs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index 5037150..f2f9b4b 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -99,13 +99,14 @@ Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to det
This specification defines a method for a {{Document}} to request access to its [=unpartitioned data=] when later used in a [=third party context=] whose active Document's origin is same-site with a specified site ({{Document/requestStorageAccessUnderSite()}}). -This specification additionally defines a method for a Document to allow such requests to succeed with arguments that are same-site to its origin in a [=browsing context=] whose active Document's origin is same-site with a specified site ({{Document/allowStorageAccessRequestOnSite()}}). +This specification additionally defines a method for a Document to allow such requests to succeed with arguments that are same-site to its origin in a [=/browsing context=] whose active Document's origin is same-site with a specified site ({{Document/allowStorageAccessRequestOnSite()}}).
Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and navigates to `https://identity.example` when the returned promise resolves. -Once navigated, Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the [=browsing context=] navigates, returning Alex to `https://site.example`. +Once navigated, Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. +Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the [=/browsing context=] navigates, returning Alex to `https://site.example`. When Alex returns to `https://site.example` requests to `https://identity.example` will bear the cookie set in the previous [=window=]. @@ -244,11 +245,11 @@ When invoked on {{Document}} |doc|, the al 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. -1. Let |parsedURI| be the result of parsing |embeddedURI|. +1. Let |parsedURI| be the result of running the [=URL parser=] on embeddedURI. 1. If |parsedURI| is failure, [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. 1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. -1. Let |parsedOrigin| be the result of obtaining the origin of |parsedURI|. +1. Let |parsedOrigin| be the |parsedURI|'s origin. 1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. @@ -263,17 +264,15 @@ When invoked on {{Document}} |doc|, the al 1. [=Resolve=] p. 1. Return |p|. -ISSUE: Deserialization of site is not well defined - When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString topLevelURI) method must run these steps: 1. Let |p| be [=a new promise=]. 1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. -1. Let |parsedURI| be the result of parsing |embeddedURI|. +1. Let |parsedURI| be the result of running the [=URL parser=] on topLevelURI. 1. If |parsedURI| is failure, [=reject=] and return |p|. 1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. 1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. -1. Let |parsedOrigin| be the result of obtaining the origin of |parsedURI|. +1. Let |parsedOrigin| be the |parsedURI|'s origin. 1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. 1. Let |settings| be |doc|'s [=relevant settings object=]. 1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. @@ -300,8 +299,6 @@ When invoked on {{Document}} |doc|, the re 1. [=Save the storage access flag set=] for |key| in |map|. 1. Return |p|. - -

User Agent storage access policies

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. From 6aabaea3dad0cd5658bfff91bee7ce2f564acf6c Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Tue, 28 Jun 2022 15:12:59 -0400 Subject: [PATCH 9/9] Reverse the order of the requests in the embedee and top level --- storage-access.bs | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/storage-access.bs b/storage-access.bs index f2f9b4b..7222c3a 100644 --- a/storage-access.bs +++ b/storage-access.bs @@ -98,17 +98,16 @@ Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to det
-This specification defines a method for a {{Document}} to request access to its [=unpartitioned data=] when later used in a [=third party context=] whose active Document's origin is same-site with a specified site ({{Document/requestStorageAccessUnderSite()}}). -This specification additionally defines a method for a Document to allow such requests to succeed with arguments that are same-site to its origin in a [=/browsing context=] whose active Document's origin is same-site with a specified site ({{Document/allowStorageAccessRequestOnSite()}}). +This specification defines two methods that must be called sequentially in top-level contexts to request access to its [=unpartitioned data=] without using iframes, {{Document/completeStorageAccessRequestFromSite()}} and {{Document/requestStorageAccessUnderSite()}}.
-Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, calls `{{Document.allowStorageAccessRequestOnSite("https://identity.example")}}` and navigates to `https://identity.example` when the returned promise resolves. +Alex visits `https://site.example`. Alex clicks "Login with Identity", which Alex expects to log them in with their account on `https://identity.example`. The page, `https://site.example`, navigates to `https://identity.example` when the returned promise resolves. -Once navigated, Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`. +Once navigated, Alex selects the account to log in with. In response, `https://identity.example` sets a cookie, which by virtue of its [=first-party-site context=] is in its [=unpartitioned data=] and calls `{{Document.requestStorageAccessUnderSite("https://site.example")}}`, which could ask Alex to give permission for the request. Once the {{Promise}} returned by `{{Document/requestStorageAccessUnderSite()}}` resolves, the [=/browsing context=] navigates, returning Alex to `https://site.example`. -When Alex returns to `https://site.example` requests to `https://identity.example` will bear the cookie set in the previous [=window=]. +When Alex returns to `https://site.example`, the page calls `{{Document.completeStorageAccessRequestFromSite("https://identity.example")}}`. Once the returned promise resolves, requests to `https://identity.example` will bear the cookie set in the previous [=window=].
@@ -178,8 +177,8 @@ To save the storage access flag set for a [=partit partial interface Document { Promise<boolean> hasStorageAccess(); Promise<undefined> requestStorageAccess(); - Promise<undefined> allowStorageAccessRequestOnSite(DOMString embeddedURI); Promise<undefined> requestStorageAccessUnderSite(DOMString topLevelURI); + Promise<undefined> completeStorageAccessRequestFromSite(DOMString embeddedURI); }; @@ -241,29 +240,6 @@ When invoked on {{Document}} |doc|, the re ISSUE: Shouldn't step 3.7 be [=same site=]? -When invoked on {{Document}} |doc|, the allowStorageAccessRequestOnSite(DOMString embeddedURI) method must run these steps: - -1. Let |p| be [=a new promise=]. -1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] and return |p|. -1. Let |parsedURI| be the result of running the [=URL parser=] on embeddedURI. -1. If |parsedURI| is failure, [=reject=] and return |p|. -1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. -1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. -1. Let |parsedOrigin| be the |parsedURI|'s origin. -1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. -1. Let |settings| be |doc|'s [=relevant settings object=]. -1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. -1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |parsedOrigin|. -1. Let |key| be the [=partitioned storage key=] (|site|, |embeddedSite|). -1. Let |global| be |doc|'s [=relevant global object=]. -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. Set |flag set|’s [=has allow request storage access flag=]. - 1. [=Save the storage access flag set=] for |key| in |map|. - 1. [=Resolve=] p. -1. Return |p|. - When invoked on {{Document}} |doc|, the requestStorageAccessUnderSite(DOMString topLevelURI) method must run these steps: 1. Let |p| be [=a new promise=]. @@ -287,18 +263,42 @@ When invoked on {{Document}} |doc|, the re 1. If |flag set|’s [=has storage access flag=] is set, 1. [=resolve=] |p| 1. return |p|. -1. If the |flag set|'s [=has allow request storage access flag=] is not set, - 1. [=reject=] |p| - 1. 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. Set |flag set|'s [=has storage access flag=]. + 1. Set |flag set|'s [=has allow request storage access flag=]. 1. Resolve |p|. 1. [=Save the storage access flag set=] for |key| in |map|. 1. Return |p|. + +When invoked on {{Document}} |doc|, the completeStorageAccessRequestFromSite(DOMString embeddedURI) method must run these steps: + +1. Let |p| be [=a new promise=]. +1. Let |parsedURI| be the result of running the [=URL parser=] on embeddedURI. +1. If |parsedURI| is failure, [=reject=] and return |p|. +1. If |doc|'s [=Document/browsing context=] is not a [=top-level browsing context=], [=reject=] and return |p|. +1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=reject=] and return |p|. +1. Let |parsedOrigin| be the |parsedURI|'s origin. +1. If |doc|'s [=Document/origin=] is same site to |parsedOrigin|, [=reject=] and return |p|. +1. Let |settings| be |doc|'s [=relevant settings object=]. +1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=]. +1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |parsedOrigin|. +1. Let |key| be the [=partitioned storage key=] (|site|, |embeddedSite|). +1. Let |global| be |doc|'s [=relevant global object=]. +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 [=has allow request storage access flag=] is set, + 1. Unset |flag set|'s [=has allow request storage access flag=]. + 1. [=Save the storage access flag set=] for |key| in |map|. + 1. [=Resolve=] p. + 1. Otherwise, + 1. [=Reject=] p +1. Return |p|. + +

User Agent storage access policies

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.