w3c / clipboard-apis

Clipboard API and events
https://w3c.github.io/clipboard-apis/
Other
149 stars 36 forks source link

Clipboard Permission #51

Open garykac opened 7 years ago

garykac commented 7 years ago

I'm working to setting on a set of Permissions for the Clipboard API. Currently, we have a placeholder in the Permissions API:

Current Proposal

Because the Permissions tend to be hierarchical (with a generic top-level permission and flags for special access - see Permissions for Midi), we were settling on:

{ name: "clipboard" } <- for basic Write access { name: "clipboard", allowRead: true } <- for both read/write access

Since read access is more of a privacy risk than write (eg: reading passwords and PII from clipboard).

Each of these permission descriptors is set to either GRANTED, DENIED or PROMPT. So, a user agent could (for example) decide to have the following defaults:

{ name: "clipboard" } = GRANTED { name: "clipboard", allowRead: true } = PROMPT

And the "allowRead=true" version is known to be "stronger" than the "allowRead=false" version so the user would not be queried if a "strong" permission was already granted.

Mozilla Permissions

Mozilla's clipboard permissions are basically:

Which makes sense as 2 independent bools, but doesn't really fit in to the basic-access/specific-access model common in Permissions.

But the Proposed Permission model described above doesn't map cleanly to Mozilla's current permissions since there is no way to grant Read access but deny Write access.

What if we had separate flags for read and write?

An alternate way of doing Permission (that we originally considered) is to instead have:

{ name: "clipboard", write: true/false, read: true/false }

This more closely matches with Mozilla has but that allows weird combinations like:

{ name: "clipboard", write: false, read: false } = GRANTED

What would this even mean? And how would it be different from having the other permissions set to DENIED?

And the "strength" relationship between the permissions in this scenario is less obvious:

write:true, read:true > write:false, read:false write:true, read:true > write:true, read:false write:true, read:true > write:false, read:true but... write:false, read:true ??? write:true, read:false write:true, read:false ??? write:false, read:true

which means we might end up asking for permission more often (eg: asking the user for write access, then asking for read).

These oddities were why we went with the Proposal mentioned earlier.

My question is: Does the original Proposal work for Mozilla? How important is being able to grant read, but not write access? @masayuki-nakano @smaug----

hallvors commented 7 years ago

(Not on behalf of Mozilla) Are you keeping the feature where read access is "implicitly" granted during a user-triggered paste?

annevk commented 7 years ago

cc @ehsan @martinthomson

garykac commented 7 years ago

@hallvors

Still under discussion, but our current thoughts are:

For a user-triggered paste (where the UA knows it's a paste), it should also be OK with a gesture, but note that this would not apply for apps that create their own custom "paste" button.

One thing we're not sure about is how much of that (the user gesture requirement) should be an explicit requirement in the spec, and how much should be left as optional for browsers. That depends in part on whether or not there's agreement. I've created a separate bug to track this.

Note: This behavior is probably more clear if we rename "allowRead" to be "fullAccess". So that the basic { clipboard } permission controls write access to the clipboard and { clipboard fullAccess=true } grants read and write without requiring a gesture.

danburzo commented 7 years ago

@garykac sorry if this is out of scope, but will the Clipboard Permission somehow incorporate access to more MIME types? (See: https://github.com/w3c/clipboard-apis/issues/46)

garykac commented 7 years ago

@danburzo The issue of mimetypes is orthogonal, although once we have clipboard access covered by a Permission it may be easier to reach some consensus on support for other mimetypes.

garykac commented 7 years ago

@Rob--W Do you have any comments on this Permission structure for Clipboard?

garykac commented 7 years ago

A initial draft of the Clipboard permission algorithm is at: https://w3c.github.io/clipboard-apis/#clipboard-permissions

Note that this draft uses { access: "full" } and { access: "write" } as suggested by @marcoscaceres. These behavior is the same.

Rob--W commented 7 years ago

Just to clarify, "Mozilla's permissions" as mentioned above are NOT web-exposed. They are only in extensions, and largely based on the usage in Chrome extensions. In practice, Google Chrome's extensions can write without any permissions (for back-compat because of an earlier mistake to not constrain it, https://chromium.googlesource.com/chromium/src/+/3a3410e0eb66727afa4f2557954ecfbd9b230c83/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/test.js#40) and only read with the clipboardRead permission. In contrast, Firefox enforces clipboardWrite and clipboardRead separately.

Reading is certainly powerful (passwords on clipboard), but writing should not underestimated either. E.g. if someone is known for the habit of pasting commands in a shell, then the ability to silently write to the clipboard can have undesired consequences. Succinctly, the capabilities of the clipboard are:

read - protects the confidentiality of the clipboard content write - protects the integrity of the clipboard content

Both goals can independently be desirable to the user (and the permisisons registry does not have any examples of permissions with read/write flags, so we're going to be the first).


The example cited as "less obvious" seems obvious to me:

write:true, read:true > write:false, read:false write:true, read:true > write:true, read:false write:true, read:true > write:false, read:true but... write:false, read:true ??? write:true, read:false write:true, read:false ??? write:false, read:true

In the above example, "???" means "not stronger", i.e. ask for permission. If the application intended to use both permissions, then it should not have downgraded its permissions to avoid addiitonal permission prompts.


An alternative to avoid the non-sensical write:false, read:false combination is to use something like:

clipboard access=full | read | write

The hierarchy is a diamond:

     full (read+write)
    /     \
write    read
    \     /
    denied
garykac commented 7 years ago

Thanks for the response. That all sounds reasonable to me. I was trying to gauge how important is was to have separate read and write permissions. It sounds like you have a (strong?) preference for keeping the ability to grant read and deny write. That works for me (I'll confirm with others).

In summary, that means the permissions would be: { name: "clipboard", access: "full" } { name: "clipboard", access: "write" } { name: "clipboard", access: "read" }

Each of these could be either "granted", "prompt", or "denied" (with the obvious strength relationship between "full" and the others).

{ name: "clipboard" } would not be allowed (i.e.: we have no default if access is unspecified).

raymeskhoury commented 7 years ago

One interesting thing to not is that "full" might just be syntactic sugar. If there was just: access: "read" access: "write"

I think all the same operations would still be possible.

garykac commented 7 years ago

"full" is useful so that you can request "read" and "write" access at the same time (e.g.: with navigator.permissions.request()).

Without it, you need to request each permission separately, which might mean querying the user twice.

garykac commented 7 years ago

It was pointed out to me that there is a discussion to remove permissions.request() from the Permissions API.

Without the request() API, we have 2 problems with the Clipboard Permissions as proposed:

My preference is to maintain support for permissions.request(), but I'm interested in any other suggestions to address these issues.

(E.g., we could add a requestClipboardPermissions() API, but my understanding is that a goal of the Permissions API is to avoid things like that.)

jyasskin commented 7 years ago

We do need either 'full' or an array in the Descriptor, in order to support permissions.query().

I'm not that worried about the read:false, write:false combination: it'd be unconditionally granted, and query() would never return it. That said, read/write/full is fine with me too.

I expect the Mozilla folks who dislike permissions.request() to keep disliking it, so it's safest for you to put a requestClipboard() function in this spec for now.

garykac commented 7 years ago

sgtm

raymeskhoury commented 7 years ago

Hmm, I'm not convinced that either of those problems are going to be concerns in practice (at least initially). I also think that if we add a requestClipboard() function it's going to tickle the same concerns that Mozilla's had with a general request() function and so I don't think it's actually any better to do that.

I would also add that request() functions that don't return a capability to operate on don't seem like an ideal design. If we had requestClipboard() that returned a clipboard object that could be operated on, it would be a more sensible API.

Unless we go down that path, I think it would be better to try to get away without adding a request() function. The biggest danger would be that in some browser users would see 2 prompts for read() and write(). But I can't imagine any browsers having such an implementation in practice. I still feel that listening to the change event should just be tied to the read permission but I'd be interested to hear use cases for keeping them separate.

martinthomson commented 6 years ago

There seem to be several things that these permissions are intended to govern. But I don't see any motivation for any of those things.

It's not clear to me whether the last item is being considered here or not, but this seems to be entirely predicated on the assumption that the asynchronous API is useful. But I couldn't find any motivation for that API at all. Can someone provide some justification for providing access to:

It also seems like reading or writing media types other than those listed as being safe is also being considered here, that should be made clearer.

It seems to me that all of the above can be managed in different ways. Some by doing nothing, which is generally the best plan if at all possible.

I note that there is no restriction on use of the clipboard by sites that don't have input focus. I don't think that sites should be able to read or write unless they have focus. Just as they don't receive keyboard and other input events. This suggests that a blanket permission, and a simple "site has permission to read" - as implied by this thread - isn't the only prerequisite for access.

garykac commented 6 years ago

For background, see the WICG proposal.

clipboardchange is required for apps that synchronize the clipboard (e.g., remote access or pasteboard sync) need this to know when to send the updated clipboard contents to the secondary system. Note that this event also requires the ability to read/write without a gesture (it doesn't contain any clipboard data), so it would need to be followed by a call to clipboard.read().

Supporting other media types is not being considered here.

I agree that clipboard access should be restricted to the tab with input focus.

martinthomson commented 6 years ago

Clipboard sync applications are fine as web extensions, but I don't think that a generic web application needs that capability.

Note that if you restrict to sites with input focus, you don't get to build a clipboard sync app. Well, it would be a pretty bad one.

garykac commented 6 years ago

Remote access is a counterexample. It is perfectly acceptable (and desirable) to only sync the clipboard with the remote machine when the tab has input focus.

martinthomson commented 6 years ago

Remote access could manage without asynchronous access to the clipboard, I think.

jyasskin commented 6 years ago

IIUC, the synchronous API is only enabled during certain browser-fired events that happen on ^c, ^v, and browser menus. However, in a remote access situation, the user might select some menu items on the remote side that mean to paste data there. Clicking some site-drawn pixels isn't enough to enable the synchronous API, so there needs to be something new.

Btw, I don't think you object to having clipboard access via Promise-based APIs. I think you'd object just as much to granting access to the clipboard in new less-constrained contexts even if that access happened synchronously. If that's the case, can we stop talking about synchronous vs asynchronous access and talk about the new contexts instead?

@garykac All of this discussion should end up in your explainer, so that you don't need to have it again with the next Martin. 😃

martinthomson commented 6 years ago

@jyasskin I get that, but none of that justifies carte-blanche access to the clipboard. It might justify access based on a gesture, but that probably needs a second guard.

jyasskin commented 6 years ago

I think remote access can manage with 1) a permission prompt for clipboard-read access and 2) still only allow reads during user activation. I've asked Gary for a counterexample. I think requiring a gesture for writes is also ok, but again there may be a counterexample.

If that's sufficient, does it feel like enough value for the risk?

garykac commented 6 years ago

Syncing the clipboard in remote access (or whatever) is bi-directional.

To sync from local -> remote, you need:

To sync from remote -> local, you need:

The ability to access the clipboard without a gesture should require an explicit permission to be granted.

The event/API should only be available to the currently active tab. Note that the new clipboard APIs are only allowed in a secure context (which is not true for the old clipboard APIs).

jyasskin commented 6 years ago

@garykac Why does local->remote have to happen instantly when the local clipboard changes? Why not wait until the next gesture?

garykac commented 6 years ago

@jyasskin: Waiting until the next gesture means:

(1) We'll be reading from the clipboard on unrelated user input events. This is a violation of the assumption that the gesture is related to the user granting clipboard access. Cf., if a site had a generic "Welcome to our site" dialog with an "OK" button and it read from the clipboard as part of the OK click handler -- that sounds like abuse to me. We shouldn't be encouraging that.

(2) Any UI on the remote machine that adapts to the clipboard contents (e.g., cut/copy/paste buttons in a formatting bar) will be incorrect until the user clicks somewhere.

(3) If the first click maps to a clipboard paste button on the remote machine, then the clipboard contents will not have a chance to be copied over before the remote copy action happens.

I created a document that gives more details: https://docs.google.com/document/d/1BknrnJqxkix01Yo6Xn9KIIN4tw1YmTjPEOKB43YZorA/edit?usp=sharing

jyasskin commented 6 years ago

Thanks! The contents of that document should live in this repository, either as part of the main spec, or in an explainer/rationale document, so that it's easier for future Martins to find it.

@martinthomson, how do you feel about Gary's reasons for reading as soon as the clipboard changes?

garykac commented 6 years ago

Yes, I'll do that. It's in a doc for the time-being so that it's easier for people to comment on specific parts.

Also, I want to emphasize that I'm not saying that we should never require a gesture. But there should be a way to request full clipboard read/write access that doesn't require a gesture.

For example, in Chrome, we are considering allowing Write by default with just a Gesture. But if you request and are granted Full access to the clipboard, then you can write (and read) without gesture.

raymeskhoury commented 6 years ago

I see a few different approaches we could take to making it possible to request gestureless access.

Question 1: How do developers request gestureless access?

Approach 1: Have a clipboard.request() function that permits obtaining gestureless access.

The advantage here is that it doesn't require changes to the design of the clipboard API. It also provides a clear way to request "full"/gestureless access. The main disadvantage here is that I think a request() function that does not return anything is very poor API design and has the disadvantages listed in Permissions #83.

Approach 2: clipboard.write() and clipboard.read() are already planned to implicitly request permission. If clipboard.read() or clipboard.write() are called outside of a user gesture, then we could request gestureless access which may trigger a prompt.

The advantage of this approach is that permission elevation is implicit in use of the API. The disadvantage is that it may be non-obvious to developers when prompts will be shown in different circumstances (browser variations, user-gesture variations, etc.). The other big problem is that it's still not clear how to get read and write access together - both functions would have to be called simultaneously which would be unnatural use of the API.

Approach 3: Change the design of the clipboard API such that requestClipboard returns a handle to the clipboard instead of it hanging off navigator.

The advantage here is that requesting permission is tied to resource acquisition. It's also very clear/simple how to request "full"/gestureless access. The main disadvantage is that it would likely require substantial changes to the spec design and implementation.

Question 2: How do we represents the permission states that allow gestureless access?

Approach 1: read may require a gesture even when granted (UA-dependent) write may require a gesture even when granted (UA-dependent) full may require a gesture (UA-dependent)

The idea is that in Chrome, read would require a gesture when granted, but write and full would not require a gesture. The advantage here is a simpler permission descriptor. The disadvantage is that because user gesture behavior will vary across browsers, developers will have to have the specific rules per browser baked into their applications and have no good way to query this behavior at runtime.

Approach 2: Have an extra field in the PermissionDescriptor called requiresGesture. The behavior of read, write and full depend on requireGesture.

The advantage here is that developers can query access to gestureless behavior at runtime. The disadvantage is a more complicated permission descriptor. Also, user gesture behavior becomes a part of the API which may or may not be desirable.

@garykac @jyasskin does the above sound accurate to you? Do you see other alternatives?

This sounds very much like an API-design question in some respects and I'm wondering if TAG would be helpful?

garykac commented 6 years ago

Q1

Approach 1 The main disadvantage mentioned here is effectively saying that Approach 3 is preferred. Unfortunately, there are issues with doing that for the clipboard API (see discussion under Approach 3).

WRT the concerns in Permissions #83, I think including text in the specification to the effect of:

"Normally, you will not need to call the request() method directly because the clipboard APIs will automatically request permission as appropriate. Scenarios where you might need to use this method include ..."

...would be sufficient.

Approach 2 Per the description above, while Approach 1 "provides a clear way" for developers to use it, this approach has parts that are "non-obvious to developers" and others parts that are "not clear" or "unnatural".

Personally, I think this is a poorer API design because it only makes a subset of the intended usage easy and makes other intended usage awkward and non-intuitive.

Note that the stated advantage of this approach ("permission elevation is implicit in the API") is also true in Approach 1 for the standard use cases.

Approach 3 In our case, the request() call can't return a handle to a resource because we need this Permission to apply to the already-existing sync clipboard APIs (which are not accessed via navigator.clipboard so they wouldn't have the right scope) and the clipboardchange event. I think the best we can do here is to return a Promise that indicates success.

However, I agree that this would certainly be the best approach if we were starting from scratch and didn't have to worry about the existing clipboard APIs.

So, my preference here (for Clipboard) is for Approach 1.

Q2

Approach 1 seems straightforward and gives the UAs flexibility to do what they need to do. The gesture requirement can't be queried, but I can't think of a use case where that matters.

WRT Approach 2, I'm a bit concerned about how that complicates the Permission UX for the user, e.g.: how a UA would describe the difference between them and how it would decide which permission to use in calls to clipboard.read() and .write().

I don't have strong opinions on this, but I think that Approach 1 is sufficient unless we have a use-case where the Permission granularity of Approach 2 is required.

I'll also note that taking Approach 1 now doesn't preclude adding a Gesture dimension to the Permission descriptor at a later time.

garykac commented 6 years ago

I've updated the spec with the latest proposal: { access: "read/write", requireGesture: true/false } and have included examples of how this would work to support "full" access.

dway123 commented 4 years ago

The (text only) Async Clipboard API TAG Review mentioned that they had been waiting on the proposal mentioned here to be added to the spec, and the last comment on this issue mentions the spec has been updated with this proposal.

It seems this issue has been generally resolved. @garykac, should this issue be closed?