privacycg / storage-access

The Storage Access API
https://privacycg.github.io/storage-access/
214 stars 28 forks source link

Consumption of User Gesture #25

Open Brandr0id opened 4 years ago

Brandr0id commented 4 years ago

In 3.1.1. User Agent storage access policies

The user gesture appears to be consumed if a prompt is rejected but not during any implicit grant without a prompt or if a prompt is allowed.

  1. If user expression of permission is false, let w be doc’s Window object and run these steps:
    1. If w has transient activation and user expression of permission is false, consume user activation with w.

This seems to be the only case the gesture would be consumed by this API, is there a specific reason to only consume it here or can we be consistent and always pass it through?

johnwilander commented 4 years ago

In 3.1.1. User Agent storage access policies

The user gesture appears to be consumed if a prompt is rejected but not during any implicit grant without a prompt or if a prompt is allowed.

  1. If user expression of permission is false, let w be doc’s Window object and run these steps:

    1. If w has transient activation and user expression of permission is false, consume user activation with w.

This seems to be the only case the gesture would be consumed by this API, is there a specific reason to only consume it here or can we be consistent and always pass it through?

The specific reason is that if the user has explicitly tapped/clicked "Don't allow," the iframe should not be able to continue and call other APIs which require a user gesture, such as open a popup, without acquiring another gesture. We've already seen misuse of the API with infinite prompting for instance.

Brandr0id commented 4 years ago

Thanks for clarifying.

The specific reason is that if the user has explicitly tapped/clicked "Don't allow," the iframe should not be able to continue and call other APIs which require a user gesture, such as open a popup

I don't know that I have a strong opinion on this either way but this seems slightly separate from the concern/abuse seen with infinite prompting from this API. Should callers not be allowed to have a fallback mechanism where if storage access is denied they open a popup or take another action where they may have access to storage and can provide an alternate experience?

In either case does it make sense to consume, or not consume, the gesture equally in the implicit and explicit deny scenarios?

We've already seen misuse of the API with infinite prompting for instance.

Agreed we don't need any prompt loops. Is this solved by the explicit deny scenario setting the flag on the document to prevent future API calls and immediately rejecting the promise?

jackfrankland commented 4 years ago

A third party can hang both the requestStorageAccess and the window.open from the same user action:

button.onclick = () => {
  const popup = window.open('./popup.html', '_blank');
  document.requestStorageAccess()
    .then(() => {
      popup.close(); // or integrate a nicer user journey to close the window
    })
    .catch(() => {
      // leave the popup open
    });
});

It's ugly, but it's viable and would be the only remaining option a third party has instead of requiring two user actions, and I think it's likely to be utilised. Perhaps it's better not to consume the user action, and prevent the prompt from reappearing. I thought there was a limit already in place in Safari?

annevk commented 4 years ago

A popup also consumes so that's not a viable workaround (fortunately).

jackfrankland commented 4 years ago

@annevk are these methods supposed to consume the gesture synchronously then, rather than the consumption being "non-propagation" of user gesture in the async callback?

My testing of the current implementation shows that requestStorageAccess can resolve after window.open has been invoked.

annevk commented 4 years ago

Yeah, I think that's how user activation is supposed to work (model is detailed in the HTML Standard, but not all APIs have explicitly adopted it yet).

jackfrankland commented 4 years ago

OK great. I wasn't aware of this, thanks.

At the risk of adding further noise... because the requestStorageAccess method itself will not consume the user activation (only the explicit reject will, from a non-blocking prompt), the following is still viable I believe, even if window.open were to consume it:

button.onclick = () => {
  let popup;
  document.requestStorageAccess()
    .then(() => {
      setTimeout(() => {
        popup.close(); // or integrate a nicer user journey to close the window
      }, 0);
    })
    .catch(() => {
      // leave the popup open
    });
  popup = window.open('./popup.html', '_blank');
};
annevk commented 4 years ago

Wouldn't it be an implementation bug to not consume it call time? The whole idea is to prevent that kind of abuse.

jackfrankland commented 4 years ago

My understanding of the proposed spec is that the calling of requestStorageAccess will not consume the user gesture, to allow for user gesture dependent APIs to be called when the user grants access, or, apparently, access is denied implicitly.

johnwilander commented 4 years ago

@jackfrankland While there might be workarounds sites can pull off, the existence of those are not arguments for making the spec allow it. The intention of the behavior after an explicit rejection by the user is to not allow further API calls which require a user gesture unless the user again interacts with the iframe.

The reason why I brought up infinite re-prompting is that it has already happened and so we know we need a way for users to say “No” and move on to other things they want to do.

annevk commented 4 years ago

Why consume only at rejection time though? Why not consume immediately when the API is invoked?

Brandr0id commented 4 years ago

Why consume only at rejection time though? Why not consume immediately when the API is invoked?

I seem to recall this was previously the case in current implementations but there was a use-case that was broken in either Firefox or Safari that prompted for the gesture to no longer be consumed in the success case. @johnwilander or @ehsan may have more context on this.

My understanding is that consuming the gesture in these APIs is mostly about preventing abuse. We wouldn't want to continually open popups like we wouldn't want to continue to prompt for access in a loop. Is there a concern about chaining APIs though? It seems there are two issues we probably shouldn't conflate:

  1. Should chaining APIs that requires user gesture be allowed?
  2. How can we prevent abuse with subsequent SAA calls (e.g. in rejection scenario)?

I think for 1. not consuming the gesture in the allow scenario or when the API is first called opens the door to this being okay. For 2. we already set a flag on the Document in the prompt rejection scenario. I believe this may be enough by itself to prevent abuse of the prompt/API since subsequent calls during this or future gestures will be rejected for the lifetime of the current Document.

othermaciej commented 4 years ago

Some embedded widgets want to pop up UI in a separate window to let the user log in, after requesting storage access and discovering that the user isn't logged in to the service. They don't want to make the user click the widget a second time in that case, which seems reasonable. This is only relevant after an acceptance. I think that's the use case we want to preserve.

Not sure if this is best done by consuming user gesture on reject only, or consuming it on call and then creating a new fake user gesture only when the promise is fulfilled without rejection.

annevk commented 4 years ago

I wonder if @mustaqahmed could chime in here. It does seem lot cleaner to me that you consume directly and then unconsume/fake a gesture in the task that fulfills the promise allowing another bit of UI.

hober commented 4 years ago

@annevk, Gecko currently propagates the user gesture to the resolve handler; see dom/base/Document.cpp#l15620.

mustaqahmed commented 4 years ago

If we are consuming here only to prevent "permission spamming", shouldn't it be handled through some intervention on Permissions API? FYI, there is an ongoing discussion to let Permissions "handle" the user activation dependency for APIs like these.


Re fake user activation: Unfortunately we don't see it as a possible solution here. The user activation spec allows activation triggering only through real user interaction. The idea of faking (or the ability to un-consume) seems to clobber the triggering mechanism. For example, it is not clear how a fake-trigger would work in a frame tree after the consumption operation had reset the state across the frame tree. Then there is the possibility that a malicious site may exploit a slow-promise-fulfillment to extend the expiry time of the original user activation. (I think we also have to be careful with multiple async APIs trying to fake in parallel, but I couldn't come up with an example.)

There is a general sentiment in the spec community that the browser should represent "the user" when it comes to user activation state, so letting developers control the state sounds terrible. We (Chrome) agreed to this point after long discussions on attempted delegation mechanisms (e.g. here).

hober commented 4 years ago

If we are consuming here only to prevent "permission spamming", shouldn't it be handled through some intervention on Permissions API?

Maybe. We're tracking possible integration with the Permissions API in #32.

annevk commented 4 years ago

@mustaqahmed I'm not entirely convinced those concerns apply. I don't think explicit propagation is needed/expensive as all these origins share the window agent. They would have synchronous access to any such state. And in this case the site ends up with UI and can then on success do something else. If instead we don't consume (or only consume upon rejection), there's much more potential for abuse between showing the UI and ending up with a rejection.

mustaqahmed commented 4 years ago

I see, the consumption idea here is unlike the regular consumption. Perhaps it is like "pausing" the user activation for the current origin---did I get your thoughts right?

annevk commented 4 years ago

Yeah, paused-pending-a-decision would be another way of describing this.

johannhof commented 3 years ago

So to me it feels like this pausing mechanism/user activation continuation should be specified more closely before we can consider using it here. If I'm reading the comments correctly then Safari isn't particularly concerned about the corner cases and I would agree that there's no point in spending a lot of effort in perfecting this protection when there are other easy ways to obtain and abuse user activation, for a determined attacker. The simple consumption on failure is definitely worth doing and this is tracked in https://bugzilla.mozilla.org/show_bug.cgi?id=1557097 on Firefox side, which I hope we can bump the priority on.

johannhof commented 3 years ago

Which is to say I'm not sure this needs the "interop" label because Firefox thinks this is valid behavior and we just haven't gotten around to it yet :)

johannhof commented 3 years ago

...though I realize that Edge doesn't seem to align on this either. Let's have the label.

annevk commented 3 years ago

It seems continuation would be a matter of invoking "activation notification" at the appropriate time and that's it. It's probably worth bringing such usage to the attention of whatwg/html though.

johannhof commented 3 years ago

In our ad-hoc meeting we decided that we want to leave consumption on explicit rejection in the spec for now, pending a more thorough discussion of a "pausing"/reactivation mode for user activation that should happen at whatwg/html.

@annevk would you have time to file an issue for that? :)

johannhof commented 3 years ago

This issue recently came up again on Firefox side as we're trying to address some open bugs in our SAA implementation. I filed https://github.com/whatwg/html/issues/7192 for getting some clarity on the "pause" model, but did not CC everyone in this issue so please subscribe as needed :)

hober commented 2 years ago

From the editors' call today: This is blocked on whatwg/html#7192; we'll be able to resolve on this side once that gets resolved on the HTML side.