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

Add permissions patches #74

Merged
merged 29 commits into from
Apr 30, 2023
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1dbc531
start work
blu25 Apr 12, 2023
e6fab75
continue work
blu25 Apr 13, 2023
0be10c0
fix failing build
blu25 Apr 13, 2023
238f436
fix failing build
blu25 Apr 14, 2023
3f18b19
add permissions gating algorithm
blu25 Apr 14, 2023
60f812f
Merge branch 'master' into liam-permissions
blu25 Apr 21, 2023
29ecb23
address review comments
blu25 Apr 24, 2023
6ef8d10
plug in config object and move change the caller of one of the checks
blu25 Apr 25, 2023
7c8a3ba
Merge branch 'master' into liam-permissions
blu25 Apr 25, 2023
5b6b0f5
Some cleanups
domfarolino Apr 26, 2023
b78ea8b
More fixes
domfarolino Apr 26, 2023
33fd180
Add permissions policy spec ref
domfarolino Apr 26, 2023
8706212
Merge branch 'master' into liam-permissions
domfarolino Apr 26, 2023
e24bf6c
Small nits/fixes
domfarolino Apr 26, 2023
c6644e7
Use navigable active document instead of target browsing context, sin…
domfarolino Apr 26, 2023
bbc1ca9
refactor functions
blu25 Apr 26, 2023
0b94042
Tiny cleanup
domfarolino Apr 27, 2023
919a0af
Merge branch 'master' into liam-permissions
domfarolino Apr 27, 2023
311455e
Changes after https://github.com/WICG/fenced-frame/pull/81
domfarolino Apr 27, 2023
962de69
Update spec.bs
blu25 Apr 27, 2023
bf59afa
Merge branch 'liam-permissions' of https://github.com/WICG/fenced-fra…
blu25 Apr 27, 2023
acf08d2
refactor to only allow *
blu25 Apr 27, 2023
7d3bed4
fix failing build
blu25 Apr 27, 2023
d8115f4
only allow the special value *
blu25 Apr 28, 2023
b2a4b96
Elaborate on intro with examples
domfarolino Apr 28, 2023
05e7ec4
Document special value better
domfarolino Apr 28, 2023
642b67a
Intro and fixes
domfarolino Apr 29, 2023
b47f023
Fixes and stuff
domfarolino Apr 29, 2023
cad5853
Adjustments
domfarolino Apr 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 273 additions & 2 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
text: historyHandling; url: navigation-hh
text: referrerPolicy; url: navigation-referrer-policy
text: attempt to populate the history entry's document; url: attempt-to-populate-the-history-entry's-document
text: navigation params; url: navigation-params
for: navigation params
text: response; url: navigation-params-response
text: navigable; url: navigation-params-navigable
text: origin; url: navigation-params-origin
for: history handling behavior
text: replace; url: hh-replace
for: document state
Expand All @@ -109,6 +111,21 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
urlPrefix: nav-history-apis.html
for: Window
text: navigable; url: window-navigable
urlPrefix: webappapis.html
for: environment
text: target browsing context; url: concept-environment-target-browsing-context
urlPrefix: document-sequences.html
for: browsing context
text: active document; url: active-document
spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
type: dfn
text: queue a cross-origin embedder policy CORP violation report; url: queue-a-cross-origin-embedder-policy-corp-violation-report
text: should request be blocked due to a bad port; url: block-bad-port
spec: mixed-content; urlPrefix: https://w3c.github.io/webappsec-mixed-content/
type: dfn
text: should fetching request be blocked as mixed content; url: should-block-fetch
spec: CSP; urlPrefix: https://w3c.github.io/webappsec-csp/
type: dfn
urlPrefix: interactive-elements.html
text: accesskey attribute command; url: using-the-accesskey-attribute-to-define-a-command-on-other-elements
text: previously focused element; url: previously-focused-element
Expand All @@ -125,6 +142,16 @@ spec: RFC8941; urlPrefix: https://www.rfc-editor.org/rfc/rfc8941.html
text: structured header; url: #section-1
for: structured header
text: token; url: name-tokens
spec: permissions-policy; urlPrefix: https://w3c.github.io/webappsec-permissions-policy
type: dfn
text: ASCII-serialized policy directive; url: serialized-policy-directive
text: serialized permissions policy; url: serialized-permissions-policy
text: supported features; url: supported-features
text: declared policy; url: declared-policy
text: the special value *; url: the-special-value
text: permissions policy; url: permissions-policy
for: permissions
text: matches; url: matches
spec: CSP; urlPrefix: https://w3c.github.io/webappsec-csp/
type: dfn
text: directive value; url: directive-value
Expand Down Expand Up @@ -258,6 +285,7 @@ dl, dd {
<dd>[=Global attributes=]</dd>
<dd><code>[=width=]</code> — Horizontal dimension</dd>
<dd><code>[=height=]</code> — Vertical dimension</dd>
<dd><code><{fencedframe/allow}></code> — [=Permissions policy=] to be applied to the <{fencedframe}>'s contents</dd>
<dt>[=Accessibility considerations=]:</dt>
<dd><p class=XXX>TODO</p></dd>
<dt>[=DOM interface=]:</dt>
Expand All @@ -270,6 +298,7 @@ interface HTMLFencedFrameElement : HTMLElement {
[CEReactions] attribute FencedFrameConfig? config;
[CEReactions] attribute DOMString width;
[CEReactions] attribute DOMString height;
[CEReactions] attribute DOMString allow;
};
</xmp>
</dd>
Expand Down Expand Up @@ -344,6 +373,14 @@ The <dfn attribute for=HTMLFencedFrameElement>config</dfn> IDL attribute getter
1. <span class=XXX>TODO</span>
</div>

The <dfn element-attr for=fencedframe>allow</dfn> attribute, when specified, determines the
[=container policy=] that will be used when the [=Document/permissions policy=] for a {{Document}}
in the <{fencedframe}>'s [=fenced navigable container/fenced navigable=] is initialized. Its value
must be a [=serialized permissions policy=]. [[!PERMISSIONS-POLICY]]

The IDL attribute <dfn attribute for=HTMLFencedFrameElement>allow</dfn> must [=reflect=] the
respective content attribute of the same name.

<h3 id=dimension-attributes>Dimension attributes</h3>

This section details monkeypatches to [[!HTML]]'s <a
Expand Down Expand Up @@ -1024,8 +1061,6 @@ Note: This is because we need to ensure that we do not leak <var ignore>creator<
document's referrer|referrer=], [=Document/origin=], [=creator base url=], [=Document/policy
container=], across the fenced frame boundary.

Issue: Ensure we are doing the right thing for [=Document/permissions policy=].

<h3 id=nested-traversables>Nested traversables</h3>

<h4 id=nested-traversables-intro>Introduction</h4>
Expand Down Expand Up @@ -1731,3 +1766,239 @@ specification is printed below:
/fenced-frame/cspee.https.html
/fenced-frame/embedder-csp-not-propagate.https.html
</wpt>

<h3 id=permissions-policy-changes>Permissions Policies</h3>

*This introductory sub-section is non-normative.*

The [=policy-controlled features=] available to {{Document}}s inside of a <{fencedframe}> are
determined exclusively by the {{FencedFrameConfig}} that the <{fencedframe}> navigates to.
domfarolino marked this conversation as resolved.
Show resolved Hide resolved
Specifically, the {{FencedFrameConfig}}'s [=fencedframeconfig/config=]'s [=fenced frame
config/effective enabled permissions=] defines the exclusive list of [=policy-controlled features=]
that will be enabled in the {{Document}} (all others will be disabled).

During navigation, the {{FencedFrameConfig}}'s [=fencedframeconfig/config=] [=instantiate a
config|instantiates=] a [=navigable/fenced frame config instance=] that is stored on the [=fenced
navigable container/fenced navigable=]. This navigable's [=navigable/fenced frame config
instance=]'s [=fenced frame config instance/effective enabled permissions=] is consulted [=Should
navigation response to navigation request be blocked by Permissions Policy?|during navigation=]. A
<{fencedframe}> navigation can only succeed if the [=Document/permissions policy=] for the
navigation's resulting {{Document}} has an [=permissions policy/inherited policy=] such that the
[=inherited policy for a feature|inherited policy value=] is "`Enabled`" for each feature in the
[=fenced frame config/effective enabled permissions=]. Otherwise the environment the <{fencedframe}>
is embedded in is deemed unsuitable for the [=fenced frame config=], and the navigation is blocked.

At the same time, to make sure that a <{fencedframe}>'s embedder does not directly influence content
in the frame based on that navigation's [=navigation params/origin=] (since the origin is derived
from cross-site data), this specification modifies various [[PERMISSIONS-POLICY]] algorithms such
that a <{fencedframe}> {{Document}}'s [=permissions policy/inherited policy=] is computed without
consideration of whether its [=navigation params/origin=] is [=same origin=] with its embedder's.
Therefore a feature can only be enabled inside of a <{fencedframe}> if its embedder *explicitly*
delegates it via [=the special value *=] [=allowlist=].

Considering all of the above, we get the following interesting implications:

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
[=the special value *=], and no \`<a http-header>`Permissions-Policy`</a>\` header is served on
the <{fencedframe}> embedder, and the <{fencedframe/allow}> attribute is empty, the navigation
inside the <{fencedframe}> will succeed, and the resulting {{Document}} will be [=allowed to
use=] the [=policy-controlled feature|feature=] (i.e., it will be enabled).

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a>, and no \`<a http-header>`Permissions-Policy`</a>\`
header is served on the <{fencedframe}> embedder, and the <{fencedframe/allow}> attribute is
empty, the navigation inside the <{fencedframe}> will be blocked.

Note: This is because ordinarily this [=policy-controlled feature|feature=] would only be enabled
if the subframe's {{Document}} was [=same origin=] with its embedder, a check this specification
avoids for fenced frames, since the <{fencedframe}>s {{Document}}'s [=Document/origin=] is
derived from cross-site data. Therefore, we simply "fail close".

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a>, and the <{fencedframe}>'s <{fencedframe/allow}>
attribute contains the [=policy-controlled feature|feature=] but no [=allowlist=], the rules
described in <a href=allow-attribute-fenced-frame>The `allow` attribute section</a>, the default
[=allowlist=] for the feature will be `'src'` which is meant to represent the embedder-supplied
navigation [=URL=], for which there is none when navigating a <{fencedframe}>, as the navigation
[=URL=] is determined by the [=fenced frame config=]. The navigation will be blocked.

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a> but either the \`<a
http-header>`Permissions-Policy`</a>\` header served on the <{fencedframe}>'s embedder *OR* the
<{fencedframe/allow}> attribute sets that [=policy-controlled feature|feature=]'s [=allowlist=]
to [=the special value *=], then the navigation inside the <{fencedframe}> will succeed, and the
resulting {{Document}} is [=allowed to use=] the [=policy-controlled feature|feature=].

* If a navigation inside a <{fencedframe}> would otherwise succeed, but the [=response=] on the
navigation inside the <{fencedframe}> is served with a \`<a
http-header>`Permissions-Policy`</a>\` header that sets the [=allowlist=] to "`none`" or an
otherwise incompatible [=origin=] for a feature in the [=fenced frame config/effective enabled
permissions=], the navigation still succeeds, but the {{Document}} in the <{fencedframe}> is
**not** [=allowed to use=] the feature.

Note: This is OK because it is the <{fencedframe}>'s content *itself* that is making the decision
to disable a particular feature, not its embedder environment.

The patches in the below section "fence" the appropriate [[PERMISSIONS-POLICY]] algorithms to
achieve the outcomes described in the above explanatory content.

<h4 id=permissions-policy-patches>Algorithm patches</h4>

<div id=allow-attribute-fenced-frame algorithm=allow-attribute-fenced-frame>
Rename the <a href=https://w3c.github.io/webappsec-permissions-policy/#iframe-allow-attribute>The
`allow` attribute of the `iframe` element</a> section to "The `allow` attribute of the `iframe`
and `fencedframe` element", and rewrite the section to read:

<{iframe}> and <{fencedframe}> elements have an respective `allow` attributes (<{iframe}>:
<{iframe/allow}>; <{fencedframe}>: <{fencedframe/allow}>), which contain an [=ASCII-serialized
policy directive=].

The allowlist for the features named in the attribute may be empty; in that case, the default
value for the [=allowlist=] is "`src`", which represents the origin of the URL in the iframe's
<{iframe/src}> attribute, or the fencedframe's [=fenced frame config=].

When not empty, the <{iframe}>'s <{iframe/allow}> or <{fencedframe}>'s <{fencedframe/allow}>
attribute will result in adding an [=allowlist=] for each [=supported feature=] to the <{iframe}>
or <{fencedframe}>'s [=container policy=].
</div>

<div algorithm=create-permissions-policy>
Modify the [$Create a Permissions Policy for a navigable$] algorithm:

Given null or an element (|container|), an [=origin=] (|origin|), and an optional [=boolean=]
|fenced| that defaults to false, this algorithm returns a new Permissions Policy.

Rewrite step 1 to read:

1. [=Assert=]: if not null, <var ignore>container</var> is either a [=navigable container=] or a
[=fenced navigable container=].

Rewrite step 4 to read:

4. For each |feature| supported:

1. Let |isInherited| be the result of running [$Define an inherited policy for feature in
container at origin$] on |feature|, |container|, |origin|, and |fenced|.

2. Set <var ignore>inherited policy</var>[|feature|] to |isInherited|.
</div>

<div algorithm=create-permissions-policy-response>
Modify the [$Create a Permissions Policy for a navigable from response$] algorithm to read:

Given null, a [=navigable container=], or a [=fenced navigable container=] (|container|), an
[=origin=] (|origin|), and a [=response=] (<var ignore>response</var>), this algorithm returns a
new [=Document/permissions policy=].

Add a step before step 1 that reads:

1. Let |fenced| be true if |container| is a [=fenced navigable container=], and false otherwise.

Modify what is *now* step 2 of the algorithm to read:

2. Let <var ignore>policy</var> be the result of running [$Create a Permissions Policy
for a navigable$] given |container|, |origin|, and |fenced|.
</div>

<div algorithm=attempt-populate-history-patches>
Modify [[HTML]]'s [=attempt to populate the history entry's document=] algorithm. Add a step
before the step inside the [=queue a task|queued task=] starting with "If
|failure| is true, then:" that reads:

8. Otherwise, if the result of [=Should navigation response to navigation request be blocked by
Permissions Policy?=] given <var ignore>navigationParams</var> is "`Blocked`", then set
|failure| to true.

Note: If this algorithm returns "`Blocked`", the pre-existing {{Document}} in the <{fencedframe}>
does not stick around; an error page will be loaded.
</div>

<div algorithm=permissions-policy-block-request>
Create a new algorithm called <dfn>Should navigation response to navigation request be blocked by
Permissions Policy?</dfn> in [[!HTML]].
blu25 marked this conversation as resolved.
Show resolved Hide resolved

Given a [=navigation params=] (|navigationParams|), this algorithm returns "`Blocked`" or
"`Allowed`":

1. Let |navigable| be |navigationParams|'s [=navigation params/navigable=].

1. If |navigable| is not a [=fenced navigable container/fenced navigable=], then return
"`Allowed`".

1. Let |origin| be |navigationParams|'s [=navigation params/origin=].

1. Let |effective permissions| be the |navigable|'s [=navigable/fenced frame config instance=]'s
[=fenced frame config instance/effective enabled permissions=].

1. Let |permissions policy| be the result of [$Create a Permissions Policy for a navigable|
creating a permissions policy$] given |navigable|'s [=fenced navigable container=], |origin|,
and <var ignore>fenced</var> set to true.

Note: This is identical to the [=Document/permissions policy=] that will be created when the
navigation constructs the ultimate {{Document}} for this pending navigation. We create it now
and run tests on it since this is the appropriate time to determine if a navigation will fail,
and then throw it away. If the navigation succeeds, it will be recreated identically and
unconditionally installed on the {{Document}}.

1. Let |inherited policy| be |permissions policy|'s [=permissions policy/inherited policy=].

1. [=list/For each=] |effective permission| of |effective permissions|:

1. If |inherited policy|[|effective permission|] is "Disabled", return "`Blocked`".

1. Return "`Allowed`."
</div>

<div algorithm=define-inherited-policy-in-container-patches>
Modify the [$Define an inherited policy for feature in container at origin$] algorithm to
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to also modify this algorithm to "fence" the container policy look-up? Right now https://w3c.github.io/webappsec-permissions-policy/#define-inherited-policy-in-container looks at the container policy (which may contain 'none', self', 'src, '*', or an origin) and runs the "matches" algorithm there. I think we'll need to ensure that 'self', 'src', and "an origin" are "fenced" and fail right? Or is there a reason we don't have to do this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Filed #82 so we can just land this PR now.

read:

Given a feature (|feature|), null or a [=navigable container=] (|container|), an [=origin=] for a
document in that container (|origin|), and an optional [=boolean=] |fenced| that defaults to
false, this algorithm returns the [=permissions policy/inherited policy=] for that feature.

Rewrite step 3 to read:

3. If the result of executing [$Is feature enabled in document for origin?$] on |feature|,
|container|'s [=Node/node document=], |origin|, and |fenced| is "Disabled", return
"Disabled".

Note: We don't have to rewrite step 2, which also delegates to the same algorithm, to pass in the
|fenced| [=boolean=] because step 2 has to do with checking to see if |feature| is enabled
|container|'s [=Node/node document=], not the {{Document}} hosted *inside* |container|.

Rewrite step 7 to read:

7. If |fenced| is false, |feature|'s [=policy-controlled feature/default allowlist=] is
`'self'`, and |origin| is [=same origin=] with |container|'s [=Node/node document=]'s
origin, return `"Enabled"`.
</div>

<div algorithm=is-feature-enabled-patches>
Modify the [$Is feature enabled in document for origin?$] algorithm to read:

Given a feature (|feature|), a {{Document}} object (|document|), an [=url/origin=] (|origin|), and
an optional [=boolean=] |fenced| that defaults to false, this algorithm returns "`Disabled`" if
|feature| should be considered disabled, and "`Enabled`" otherwise.

Rewrite step 3 to read:

3. If |feature| is present in |policy|'s [=declared policy=],

1. If |fenced| is set to false, and the [=allowlist=] for |feature| in |policy|'s [=declared
policy=] [=permissions/matches=] |origin|, then return "`Enabled`".

2. Otherwise, if |fenced| is set to true, and the [=allowlist=] for |feature| in |policy|'s
[=declared policy=] is [=the special value *=], then return "`Enabled`".

3. Otherwise, return "`Disabled`".

Rewrite step 5 to read:

5. If |fenced| is set to false, |feature|'s [=policy-controlled feature/default allowlist=] is
`'self'`, and |origin| is [=same origin=] with |document|'s origin, return "Enabled".
</div>