-
Notifications
You must be signed in to change notification settings - Fork 42
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
Revisiting payment app filtering #96
Comments
If this cannot be determined from data the browser already has, and needs to execute code in the origin, a service worker event is the best fit. |
I think that changing this into an event might not be such a good idea privacy-wise. In a regular event handler, there's nothing stopping the service worker from logging all of the payment request information and passing it back to the back-end. That would let an information-greedy payment provider see all of the user's potential payments -- even the ones that don't ultimately end up in that particular payment app. The intention of the currently specified mechanism is that the registered I'm not actually a great fan of the current Since this has turned out to be very difficult to specify in a way that fulfills everyone's needs, maybe it would be better if we postponed this mechanism to version 2 of the specification? That would require that we revert some changes to the |
But the only info the SW gets is "someone wants to know if you can handle this" - not who or what or where they are trying to buy. So the info is not useful beyond "lots of sites wonder if this particular payment method is supported by me". |
In fact, there may be no intent to buy anything - because a site intimates a canMakeActivePayment() potential long before any actual payment happens (if it happens at all!). The browser knows the registered methods of the app, so it could strip out the ones that don't match. |
@tommythorsen can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code? |
@marcoscaceres wrote "The browser knows the registered methods of the app, so it could strip out the ones that don't match." We asked ourselves "who should do the filtering, the browser or the payment app?" However, we expect to see a number of payment methods in use, each with its own filtering mechanism, and if we rely on the browser, then we are not likely to see those implemented. Therefore, we have favored asking payment apps to implement the filtering. Ian |
@marcoscaceres This filtering of the Payment Request to a new object that represents a subset of the data is exactly what the current spec does and that you have insisted is a bad idea and should be taken to the TAG for review. I'm confused now as to what you're actually proposing? |
I think there is conflation of two topics:
Ian |
@ianbjacobs I disagree @marcoscaceres said, in the context of whether the data passed to the SW is private or not:
This implies he is talking about the data passed to the app which he is also advocating should be the exact same request object passed to the browser |
To be clear, this code must not execute in the origin. It needs to execute in its own unique origin. |
@adamroach can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code? |
As I'm sucking at using natural language, I'm proposing this: bank.com / index.htmlconst { paymentManager: pm } = navigator.serviceWorker;
pm.methods.set("visa-4756", {
name: "Visa ending ****4756",
methods: ["basic-card"],
icons: [visaIcons],
}); merchant.com / checkout.htmlconst methodData = [{
supportedMethods: ["basic-card"],
data: {
supportedNetworks: ['aFamousBrand', 'aDebitNetwork'],
supportedTypes: ['debit']
}
}, {
supportedMethods: ["bobpay.com"],
data: {
merchantIdentifier: "XXXX",
bobPaySpecificField: true
}
}];
const request = new PaymentRequest(method, details, options);
const canDoIt = await request.canMakePayment(); bank.coms/service-worker.js// We only get ["basic card"] methods, not bobpay.com!
addEventListener("canmakepayment", ev => {
ev.canMakePayment(new Promise(resolve => {
let canDoIt = false;
for(const {supportedNetworks, supportedTypes} of ev.methods){
if(supportedNetworks.includes("aFamousBrand") && supportedTypes.includes("debit")){
canDoIt = true;
}
}
// Could do other checks async...
resolve(canDoIt);
});
}); |
(the above should actually destructure "method.data", but you get the idea.... the point is that bank.com can do that checks it needs need against the matching payment methods sent from merchant.com - without spying on merchant.com's bobpay's stuff) |
Hi @marcoscaceres, Thanks for the code! First, bank.com is doing the evaluation of the merchant filter. That is consistent with what we've sought. Second, it seems that the browser is giving bank.com's service worker only the data associated with the payment method(s) supported by bank.com. That's also consistent with what we've sought. However, it suggests that a subset of methodData is given to the payment app, and I had the impression from another discussion you did not want to do that. Perhaps I have misunderstood one suggestion or the other. But our current preference is for the browser to give the service worker only that information relevant for the payment methods it supports. When we first started talking about service worker handling of filters, people observed the following:
Thus, your proposal reveals information about the transaction to all payment apps with People expressed privacy concerns with this approach. That's why we pursued a design where I hope that sheds light on how we ended up with what we have. I look forward to more discussion to work through this, Ian |
I like the approach of using an event (I think I have heard consensus that the group i happy with this change). What we need to maintain though is the requirement that the event handler is executed in a "sandbox" of some sort that prevents the data passed in being stored or sent over the network. As @ianbjacobs points out, this is partly based on privacy concerns, but also because this needs to return quickly as it likely impacting UI (the set of apps being presented to the user) |
The browser is not able to answer questions regarding the contents of the An example of what the contents of the data field might be, can be found in the Basic Card Payment specification. Here, information about the supported networks ("visa", "amex", "mastercard", etc) and the type of card ("credit", "debit", etc) is supplied in the Is the |
On 25 Jan 2017, at 6:46 pm, Adrian Hope-Bailie ***@***.***> wrote:
I like the approach of using an event (I think I have heard consensus that the group i happy with this change).
What we need to maintain though is the requirement that the event handler is executed in a "sandbox" of some sort that prevents the data passed in being stored or sent over the network.
We can't do that, unfortunately. That would break service workers. It would also be super easy to work around (just put everything in IDB or make a text file with the Cache API, and then send it later).
So, if there is data that the event should not expose, we should talk about that: the event *is* the sandbox.
My proposal only exposes filtered payment methods and whatever is on the DOM Event interface, from which it inherits.
As @ianbjacobs points out, this is partly based on privacy concerns, but also because this needs to return quickly as it likely impacting UI (the set of apps being presented to the user)
That's the ideal, but not what can happen in reality. For example, the service worker may need to connect to a watch or other device over Bluetooth (actually bought something today like this with my Apple Watch) or might need to verify that the payment method has balance over the network or whatever - it's up to the application, we shouldn't restrict how it checks. If it is taking too long (or the SW shuts down), then the browser can intervene appropriately.
… —
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
While this would conceptually take care of the privacy concerns, I feel that this might be too hard to implement in a secure way. Are there any examples of similar things in existence today, or are we treading new ground here? If it's the latter, then I am apprehensive about this.
The SW is installed into a browser by a web site that might have the user logged in. It also regularly opens client windows which presumably will prompt for user login. I think that the SW does know "who". As for "what" and "where", these depend on the contents of the opaque
It is likely that |
On 25 Jan 2017, at 8:01 pm, Tommy Thorsen ***@***.***> wrote:
can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code?
The browser is not able to answer questions regarding the contents of the data fields in PaymentMethodData and PaymentDetailsModifier dictionaries. The structure of the contents of these fields are proprietary, and only known by the payee (the merchant) and the payment provider (the one that owns the payment app).
An example of what the contents of the data field might be, can be found in the Basic Card Payment specification. Here, information about the supported networks ("visa", "amex", "mastercard", etc) and the type of card ("credit", "debit", etc) is supplied in the data field.
Is the canHandle() mechanism an absolute and fundamental requirement that we can't do without?
I believe it is. The Service Worker or object that the Service Worker gets representing the PaymentRequest must be able to handle all the method calls: .canMakePayment(), .abort(), .updateWith(), etc.
As I've said above, I'm not a great fan of this, and I feel that it shouldn't be necessary for the merchant to ask this question, as long as he
Or she:)
can supply a fallback option in the form of a recommended payment app to ensure that the user isn't presented with a dead end in the payment request UI.
I don't think we should do the recommended payment app thing - the merchant can recommend whatever they like (or fall back to basic card).
… —
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Well, we could remove .canMakePayment() too...
I'm not sure I follow your argumentation, but falling back to basic card is certainly also an option. I think that for the sake of kickstarting the payment app ecosystem, though, recommended payment apps has the potential to work very well. |
I think I get it, but could you provide a quick example (with code)? I understand the thing about the proprietary structures, but what isn't clear to me is how the other origin would answer that without any access to origin data or communication. It kinda feels like the recommended apps issue - too much time is being spent discussing the mechanism rather than the bigger picture.
I think it's new ground. There are worklets from the houdini project, which may be similar. However, are we sure this solves the problem, if the code has no access to network or storage? |
Ok, I'll give it a shot. But instead of using the boring basic card payment method, I'm going to use a hypothetical visa checkout payment app. I've taken some of the property names and values from the visa checkout specification, so this is not entirely unrealistic. Here's the code which the merchant uses to create a payment request: var methodData = [
{
supportedMethods: [ "https://secure.checkout.visa.com/pay" ],
data: {
apikey: "9898df897df87df987df98d7f987df",
xPayToken: "xv2:" + timestamp + ":" + hashString,
settings: {
payment: {
cardBrands: [ "VISA", "AMEX" ],
acceptCanadianVisaDebit: false
}
}
}
}
]
var details = {
total: {
label: "Total",
amount: { currency: "USD", value: "100.00" }
},
displayItems: [
{
label: "Blue suede shoes",
amount: { currency: "USD", value: "100.00" }
}
],
shippingOptions: [
{
id: "free",
label: "Free shipping!",
amount: { currency: "USD", value: "0.00" }
}
],
modifiers: []
}
var request = new PaymentRequest(methodData, details);
request.show() Everything inside |
From a spec perspective, that's probably true. As I mention above, though, the execution environment I'm proposing is identical to the execution environment PAC files run in: no network access and a unique origin. This has been implemented in browsers for decades. |
This sounds like it'd require access to origin storage, no? Could you show the code for that bit too? |
Yeah, either it needs access to storage, or the function needs to be individually constructed for the user. I have not made any attempts to explore this subject any further than this, so your attempt to implement would be as good as mine. Maybe one of the actual proponents of this mechanism would be more interested in writing some example code for this? |
While it has been implemented in browsers for decades, a quick google search for "pac file attack" makes me doubt that this is proof that it works, security-wise. On the first page, I get the following hits:
Further reading on the subject suggests that the way to stay secure w.r.t. PAC files boils down to "for god's sake, don't let PAC files from a non-trusted source onto your system", rather than "don't worry, the browser's sandbox will keep you safe". Do we think we can make our function more secure than this? My gut feeling says that it's not possible to do this in a way that's absolutely and demonstrably bulletproof. |
The design of this (if it's necessary) depends heavily on the kind of access it needs to other things. @adamroach can you pick up where @tommythorsen left off? Can you write some code that shows what a developer may need to write in a I imagine there are a few ways to solve this, and some are defined by specs already, but we shouldn't be diving into the solution until we fully understand the problem. |
@tommythorsen -- Those attacks are uniformly due to the role that PAC files play (controlling where all web traffic goes), rather than the environment they run in. |
Discussing PAC files is redundant. There are existing specs that could be used for originless computation. There's also the possibility of creating something new. Sorry to sound like a stuck record but… clarifying the problem and its requirements should be done before searching for a solution. Can we do that first? |
@rsolomakhin, I think we may need to discuss this step:
A note from previous discussions on this topic for context, an app that wishes to "farm" merchant data or always be presented to the user is simply going to claim support for as many payment methods as it knows about with the bare minimum filters. That way it increases its odds of being selected to handle a payment and can try to enroll the user for a requested method once it is invoked. (i.e. It fakes support to get selected and then tries to "wing it" once it is, by supporting basic card as a fallback). Worst case scenario it gathers data about the merchant and it's accepted payment methods. One way to counter this is to only allow the request to specify a "wildcard". i.e. A merchant can submit a request with the method Another possibility is that browsers intelligently order options presented to the user based on how often that option has resulted in a failed request in the past? |
There's no 100% guaranteed way to avoid this, but the user agent can use own signals to detect such apps and penalize them, somehow. I don't see how this affects the spec, though.
We plan to do this. |
I'll note that the algorithm I sketch out has this exact property: if a merchant specifies a filter, then the payment app must have a matching capability. If the payment app has a capability that the merchant hasn't requested, that makes no difference. Also, I think it muddies the conversation to think of these as "filters" on the app side -- they're capabilities. The merchant is specifying a filter that demands certain capabilities of payment apps in order for those apps to match. If you think of them in that way, it makes it much easier to see how they should interact. |
+1 |
It's still not clear to me if "native" payment handlers will have more capabilities to determine if
Then we are doing the web a disservice by not providing an analogous set of capabilities (via service workers). However, if the answer to |
Apple Pay has |
I rest my case. |
I'm a bit confused. Every time Nick Shearer has spoken on this topic, he's indicated that these Apple Pay methods are very limited, and effectively provide the application hints (not promises) about whether there is some kind of payment instrument registered. It doesn't even allow the requestor to indicate which kind of card they're asking about -- it's just "is Apple Pay activated on this device in a way that it could, in theory, for some card, and I don't know which one, possibly but not certainly, make a payment?" And I'm pretty confident that doing so does not involve any network interaction whatsoever. |
Reading the comments above, I sense that there is a bit of confusion around the two different pieces of functionality that can be used to check the ability to make a payment. Let me just try to make things clear:
The speed requirements on these two mechanisms are different. Since The Apple Pay methods mentioned above, are equivalent to the |
@adamroach, wrote:
This is the case for @nickjshearer? Can you kindly confirm if Apple Pay makes a network request when @tommythorsen, wrote:
This is imposing UX/UI ideas on the spec: consider, the list of payment providers could be incrementally shown or become enabled as each becomes ready: In the above, "wallet.com" is not ready yet for whatever reason.
If the above holds, then I'd be in support of @rsolomakhin's proposal |
I usually abstain from the Payment Apps discussion since I'm not taking an active role in driving it, but say my name three times and like a titular movie ghost I will be summoned. Anyway, if it will help you reach consensus here's how Apple Pay discovery in Safari currently works. In Apple Pay today, we have two functions. One is The second, more complex function, is So when @adamroach says...
...he's correct, and that's by design to protect the privacy of the user. Indeed, the user may have chosen to turn this discovery off in Safari entirely in which case it will always simply return true. But as @marcoscaceres suggests and our documentation confirms:
We perform some checks that the domain is currently validated for Apple Pay (for security merchants who take Apple Pay must prove ownership of their domain), and to make sure site owners aren't mis-using or abusing the API. So yes, it's an asynchronous call (it actually returns a promise)[1]. Like I said at the start, I'm going to abstain from any kind of recommendation, but I will say that it's a very real case that some payment apps will need to perform asynchronous work to establish whether they're available or not. Sometimes that might be for security - like Apple Pay - other times, though, it might not even be a network request, it might be something like talking to a connected piece of hardware. [1]: For those interested you can see what's actually going on between these two calls here: https://trac.webkit.org/browser/trunk/Source/WebCore/Modules/applepay/ApplePaySession.cpp#L472 |
Hi all, I'd like to summarize where I think we are on the filtering topic and Summary:
Given what I've heard so far, here's my proposal:
Ian [1] #96 (comment) |
I believe the proposal is that a merchant defines "filters" and an app defines "capabilities". |
Mentioning @adamroach and @rsolomakhin to invite their input |
@ianbjacobs, great summary. Thank you. There might be a third option:
|
I'm sorry but I'm not clear on what the third option would be. Could you say a bit more? Thank you! |
@ianbjacobs see #96 (comment) - basically what Nick describes there (trying to avoid summoning him again, so not putting in his username :)). We have the same requirements: with the "capabilities" in place, the question ("is it capable?") can be answered without needing to go wake up the service worker - however, it will need to hit the Payment Manager's Payment Methods database, which might require IO (so this still needs to be a promise, I think - tho the value could be cached, so could still be fast). On the other hand, "can I make an 'ACTIVE' payment?" needs to go do fancy things - like talk to hardware, network request, whatever)... and in the case of
In the the form of an API: //During checkout form construction
// this would be fairly fast
if (await request.isCapable()) {
// Build checkout form - show fancy buttons, but maybe disable
// some of them until request.canMakePayment() actually confirms we can do stuff.
fancyPaymentButtons.forEach(button => button.disabled = true);
if (await request.canMakePayment()) {
// Ok, let's actually enable
fancyPaymentButtons.forEach(button => button.disabled = false);
}
} |
(updated the above, to ask "is it capable?" instead... hopefully that's more clear) |
At the 14 Feb task force call [1] there was consensus to adopt the proposal I wrote above [2]. Adam will write up the parts for the payment app spec; Rouslan will write up the parts for the payment request API. There was consensus that this proposal intends to address a limited set of use cases for capability matching BEFORE user selection of payment app. For more powerful capability matching, there was consensus that the payment apps themselves should handle that once launched. Ian [1] https://www.w3.org/2017/02/14-apps-minutes#item03 |
Closing this issue as we are progressing as follows:
|
The current spec includes a function that a payment app can optionally register for itself (
canHandle()
in the existingsetManifest()
method) for the purpose of determining whether the payment app is applicable to the payment method and its filters. This is one of several mechanisms that have been discussed to date.@marcoscaceres correctly points out that, as specified, the current mechanism is somewhat problematic, in that we're assuming basically a "pickling" of this function for use in future contexts in a way that's not obviously supported by ES.
In practice, when I proposed this approach (and, to be clear, it was my second-favorite choice out of options on the table), what I had in mind was something very similar to how PAC files operate, both from a persistence and execution environment perspective. While there are probably ways to specify
canHandle()
in a way that is more palatable (e.g., by having it point to an external file that is evaluated independently), there is some question as to whether doing so is worth the effort it is likely to take.The alternate approach is to more concretely define a payment filtering algorithm based on a generic understanding of the
filter
parameter passed as part of thePaymentRequest
.I'm opening this issue to re-kindle the discussion regarding these two potential approaches, in light of the newly highlighted difficulties we are likely to encounter. Of course, alternatives approaches beyond the two I describe are welcome as well.
The text was updated successfully, but these errors were encountered: