dom/payment/Payment.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11
michael@0 12 this.EXPORTED_SYMBOLS = [];
michael@0 13
michael@0 14 const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
michael@0 15 "Payment:Success",
michael@0 16 "Payment:Failed"];
michael@0 17
michael@0 18 const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
michael@0 19 const PREF_PAYMENT_BRANCH = "dom.payment.";
michael@0 20 const PREF_DEBUG = "dom.payment.debug";
michael@0 21
michael@0 22 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
michael@0 23 "@mozilla.org/parentprocessmessagemanager;1",
michael@0 24 "nsIMessageListenerManager");
michael@0 25
michael@0 26 XPCOMUtils.defineLazyServiceGetter(this, "prefService",
michael@0 27 "@mozilla.org/preferences-service;1",
michael@0 28 "nsIPrefService");
michael@0 29
michael@0 30 let PaymentManager = {
michael@0 31 init: function init() {
michael@0 32 // Payment providers data are stored as a preference.
michael@0 33 this.registeredProviders = null;
michael@0 34
michael@0 35 this.messageManagers = {};
michael@0 36
michael@0 37 // The dom.payment.skipHTTPSCheck pref is supposed to be used only during
michael@0 38 // development process. This preference should not be active for a
michael@0 39 // production build.
michael@0 40 let paymentPrefs = prefService.getBranch(PREF_PAYMENT_BRANCH);
michael@0 41 this.checkHttps = true;
michael@0 42 try {
michael@0 43 if (paymentPrefs.getPrefType("skipHTTPSCheck")) {
michael@0 44 this.checkHttps = !paymentPrefs.getBoolPref("skipHTTPSCheck");
michael@0 45 }
michael@0 46 } catch(e) {}
michael@0 47
michael@0 48 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
michael@0 49 ppmm.addMessageListener(msgname, this);
michael@0 50 }
michael@0 51
michael@0 52 Services.obs.addObserver(this, "xpcom-shutdown", false);
michael@0 53
michael@0 54 try {
michael@0 55 this._debug =
michael@0 56 Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
michael@0 57 && Services.prefs.getBoolPref(PREF_DEBUG);
michael@0 58 } catch(e) {
michael@0 59 this._debug = false;
michael@0 60 }
michael@0 61 },
michael@0 62
michael@0 63 /**
michael@0 64 * Process a message from the content process.
michael@0 65 */
michael@0 66 receiveMessage: function receiveMessage(aMessage) {
michael@0 67 let name = aMessage.name;
michael@0 68 let msg = aMessage.json;
michael@0 69 if (this._debug) {
michael@0 70 this.LOG("Received '" + name + "' message from content process");
michael@0 71 }
michael@0 72
michael@0 73 switch (name) {
michael@0 74 case "Payment:Pay": {
michael@0 75 // First of all, we register the payment providers.
michael@0 76 if (!this.registeredProviders) {
michael@0 77 this.registeredProviders = {};
michael@0 78 this.registerPaymentProviders();
michael@0 79 }
michael@0 80
michael@0 81 // We save the message target message manager so we can later dispatch
michael@0 82 // back messages without broadcasting to all child processes.
michael@0 83 let requestId = msg.requestId;
michael@0 84 this.messageManagers[requestId] = aMessage.target;
michael@0 85
michael@0 86 // We check the jwt type and look for a match within the
michael@0 87 // registered payment providers to get the correct payment request
michael@0 88 // information.
michael@0 89 let paymentRequests = [];
michael@0 90 let jwtTypes = [];
michael@0 91 for (let i in msg.jwts) {
michael@0 92 let pr = this.getPaymentRequestInfo(requestId, msg.jwts[i]);
michael@0 93 if (!pr) {
michael@0 94 continue;
michael@0 95 }
michael@0 96 // We consider jwt type repetition an error.
michael@0 97 if (jwtTypes[pr.type]) {
michael@0 98 this.paymentFailed(requestId,
michael@0 99 "PAY_REQUEST_ERROR_DUPLICATED_JWT_TYPE");
michael@0 100 return;
michael@0 101 }
michael@0 102 jwtTypes[pr.type] = true;
michael@0 103 paymentRequests.push(pr);
michael@0 104 }
michael@0 105
michael@0 106 if (!paymentRequests.length) {
michael@0 107 this.paymentFailed(requestId,
michael@0 108 "PAY_REQUEST_ERROR_NO_VALID_REQUEST_FOUND");
michael@0 109 return;
michael@0 110 }
michael@0 111
michael@0 112 // After getting the list of valid payment requests, we ask the user
michael@0 113 // for confirmation before sending any request to any payment provider.
michael@0 114 // If there is more than one choice, we also let the user select the one
michael@0 115 // that he prefers.
michael@0 116 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
michael@0 117 .createInstance(Ci.nsIPaymentUIGlue);
michael@0 118 if (!glue) {
michael@0 119 if (this._debug) {
michael@0 120 this.LOG("Could not create nsIPaymentUIGlue instance");
michael@0 121 }
michael@0 122 this.paymentFailed(requestId,
michael@0 123 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
michael@0 124 return;
michael@0 125 }
michael@0 126
michael@0 127 let confirmPaymentSuccessCb = function successCb(aRequestId,
michael@0 128 aResult) {
michael@0 129 // Get the appropriate payment provider data based on user's choice.
michael@0 130 let selectedProvider = this.registeredProviders[aResult];
michael@0 131 if (!selectedProvider || !selectedProvider.uri) {
michael@0 132 if (this._debug) {
michael@0 133 this.LOG("Could not retrieve a valid provider based on user's " +
michael@0 134 "selection");
michael@0 135 }
michael@0 136 this.paymentFailed(aRequestId,
michael@0 137 "INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
michael@0 138 return;
michael@0 139 }
michael@0 140
michael@0 141 let jwt;
michael@0 142 for (let i in paymentRequests) {
michael@0 143 if (paymentRequests[i].type == aResult) {
michael@0 144 jwt = paymentRequests[i].jwt;
michael@0 145 break;
michael@0 146 }
michael@0 147 }
michael@0 148 if (!jwt) {
michael@0 149 if (this._debug) {
michael@0 150 this.LOG("The selected request has no JWT information " +
michael@0 151 "associated");
michael@0 152 }
michael@0 153 this.paymentFailed(aRequestId,
michael@0 154 "INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
michael@0 155 return;
michael@0 156 }
michael@0 157
michael@0 158 this.showPaymentFlow(aRequestId, selectedProvider, jwt);
michael@0 159 };
michael@0 160
michael@0 161 let confirmPaymentErrorCb = this.paymentFailed;
michael@0 162
michael@0 163 glue.confirmPaymentRequest(requestId,
michael@0 164 paymentRequests,
michael@0 165 confirmPaymentSuccessCb.bind(this),
michael@0 166 confirmPaymentErrorCb.bind(this));
michael@0 167 break;
michael@0 168 }
michael@0 169 case "Payment:Success":
michael@0 170 case "Payment:Failed": {
michael@0 171 let mm = this.messageManagers[msg.requestId];
michael@0 172 mm.sendAsyncMessage(name, {
michael@0 173 requestId: msg.requestId,
michael@0 174 result: msg.result,
michael@0 175 errorMsg: msg.errorMsg
michael@0 176 });
michael@0 177 break;
michael@0 178 }
michael@0 179 }
michael@0 180 },
michael@0 181
michael@0 182 /**
michael@0 183 * Helper function to register payment providers stored as preferences.
michael@0 184 */
michael@0 185 registerPaymentProviders: function registerPaymentProviders() {
michael@0 186 let paymentProviders = prefService
michael@0 187 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH)
michael@0 188 .getChildList("");
michael@0 189
michael@0 190 // First get the numbers of the providers by getting all ###.uri prefs.
michael@0 191 let nums = [];
michael@0 192 for (let i in paymentProviders) {
michael@0 193 let match = /^(\d+)\.uri$/.exec(paymentProviders[i]);
michael@0 194 if (!match) {
michael@0 195 continue;
michael@0 196 } else {
michael@0 197 nums.push(match[1]);
michael@0 198 }
michael@0 199 }
michael@0 200
michael@0 201 // Now register the payment providers.
michael@0 202 for (let i in nums) {
michael@0 203 let branch = prefService
michael@0 204 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH + nums[i] + ".");
michael@0 205 let vals = branch.getChildList("");
michael@0 206 if (vals.length == 0) {
michael@0 207 return;
michael@0 208 }
michael@0 209 try {
michael@0 210 let type = branch.getCharPref("type");
michael@0 211 if (type in this.registeredProviders) {
michael@0 212 continue;
michael@0 213 }
michael@0 214 this.registeredProviders[type] = {
michael@0 215 name: branch.getCharPref("name"),
michael@0 216 uri: branch.getCharPref("uri"),
michael@0 217 description: branch.getCharPref("description"),
michael@0 218 requestMethod: branch.getCharPref("requestMethod")
michael@0 219 };
michael@0 220 if (this._debug) {
michael@0 221 this.LOG("Registered Payment Providers: " +
michael@0 222 JSON.stringify(this.registeredProviders[type]));
michael@0 223 }
michael@0 224 } catch (ex) {
michael@0 225 if (this._debug) {
michael@0 226 this.LOG("An error ocurred registering a payment provider. " + ex);
michael@0 227 }
michael@0 228 }
michael@0 229 }
michael@0 230 },
michael@0 231
michael@0 232 /**
michael@0 233 * Helper for sending a Payment:Failed message to the parent process.
michael@0 234 */
michael@0 235 paymentFailed: function paymentFailed(aRequestId, aErrorMsg) {
michael@0 236 let mm = this.messageManagers[aRequestId];
michael@0 237 mm.sendAsyncMessage("Payment:Failed", {
michael@0 238 requestId: aRequestId,
michael@0 239 errorMsg: aErrorMsg
michael@0 240 });
michael@0 241 },
michael@0 242
michael@0 243 /**
michael@0 244 * Helper function to get the payment request info according to the jwt
michael@0 245 * type. Payment provider's data is stored as a preference.
michael@0 246 */
michael@0 247 getPaymentRequestInfo: function getPaymentRequestInfo(aRequestId, aJwt) {
michael@0 248 if (!aJwt) {
michael@0 249 this.paymentFailed(aRequestId, "INTERNAL_ERROR_CALL_WITH_MISSING_JWT");
michael@0 250 return true;
michael@0 251 }
michael@0 252
michael@0 253 // First thing, we check that the jwt type is an allowed type and has a
michael@0 254 // payment provider flow information associated.
michael@0 255
michael@0 256 // A jwt string consists in three parts separated by period ('.'): header,
michael@0 257 // payload and signature.
michael@0 258 let segments = aJwt.split('.');
michael@0 259 if (segments.length !== 3) {
michael@0 260 if (this._debug) {
michael@0 261 this.LOG("Error getting payment provider's uri. " +
michael@0 262 "Not enough or too many segments");
michael@0 263 }
michael@0 264 this.paymentFailed(aRequestId,
michael@0 265 "PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
michael@0 266 return true;
michael@0 267 }
michael@0 268
michael@0 269 let payloadObject;
michael@0 270 try {
michael@0 271 // We only care about the payload segment, which contains the jwt type
michael@0 272 // that should match with any of the stored payment provider's data and
michael@0 273 // the payment request information to be shown to the user.
michael@0 274 // Before decoding the JWT string we need to normalize it to be compliant
michael@0 275 // with RFC 4648.
michael@0 276 segments[1] = segments[1].replace("-", "+", "g").replace("_", "/", "g");
michael@0 277 let payload = atob(segments[1]);
michael@0 278 if (this._debug) {
michael@0 279 this.LOG("Payload " + payload);
michael@0 280 }
michael@0 281 if (!payload.length) {
michael@0 282 this.paymentFailed(aRequestId, "PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
michael@0 283 return true;
michael@0 284 }
michael@0 285 payloadObject = JSON.parse(payload);
michael@0 286 if (!payloadObject) {
michael@0 287 this.paymentFailed(aRequestId,
michael@0 288 "PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
michael@0 289 return true;
michael@0 290 }
michael@0 291 } catch (e) {
michael@0 292 this.paymentFailed(aRequestId,
michael@0 293 "PAY_REQUEST_ERROR_ERROR_DECODING_JWT");
michael@0 294 return true;
michael@0 295 }
michael@0 296
michael@0 297 if (!payloadObject.typ) {
michael@0 298 this.paymentFailed(aRequestId,
michael@0 299 "PAY_REQUEST_ERROR_NO_TYP_PARAMETER");
michael@0 300 return true;
michael@0 301 }
michael@0 302
michael@0 303 if (!payloadObject.request) {
michael@0 304 this.paymentFailed(aRequestId,
michael@0 305 "PAY_REQUEST_ERROR_NO_REQUEST_PARAMETER");
michael@0 306 return true;
michael@0 307 }
michael@0 308
michael@0 309 // Once we got the jwt 'typ' value we look for a match within the payment
michael@0 310 // providers stored preferences. If the jwt 'typ' is not recognized as one
michael@0 311 // of the allowed values for registered payment providers, we skip the jwt
michael@0 312 // validation but we don't fire any error. This way developers might have
michael@0 313 // a default set of well formed JWTs that might be used in different B2G
michael@0 314 // devices with a different set of allowed payment providers.
michael@0 315 let provider = this.registeredProviders[payloadObject.typ];
michael@0 316 if (!provider) {
michael@0 317 if (this._debug) {
michael@0 318 this.LOG("Not registered payment provider for jwt type: " +
michael@0 319 payloadObject.typ);
michael@0 320 }
michael@0 321 return false;
michael@0 322 }
michael@0 323
michael@0 324 if (!provider.uri || !provider.name) {
michael@0 325 this.paymentFailed(aRequestId,
michael@0 326 "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
michael@0 327 return true;
michael@0 328 }
michael@0 329
michael@0 330 // We only allow https for payment providers uris.
michael@0 331 if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
michael@0 332 // We should never get this far.
michael@0 333 if (this._debug) {
michael@0 334 this.LOG("Payment provider uris must be https: " + provider.uri);
michael@0 335 }
michael@0 336 this.paymentFailed(aRequestId,
michael@0 337 "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
michael@0 338 return true;
michael@0 339 }
michael@0 340
michael@0 341 let pldRequest = payloadObject.request;
michael@0 342 return { jwt: aJwt, type: payloadObject.typ, providerName: provider.name };
michael@0 343 },
michael@0 344
michael@0 345 showPaymentFlow: function showPaymentFlow(aRequestId,
michael@0 346 aPaymentProvider,
michael@0 347 aJwt) {
michael@0 348 let paymentFlowInfo = Cc["@mozilla.org/payment/flow-info;1"]
michael@0 349 .createInstance(Ci.nsIPaymentFlowInfo);
michael@0 350 paymentFlowInfo.uri = aPaymentProvider.uri;
michael@0 351 paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
michael@0 352 paymentFlowInfo.jwt = aJwt;
michael@0 353
michael@0 354 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
michael@0 355 .createInstance(Ci.nsIPaymentUIGlue);
michael@0 356 if (!glue) {
michael@0 357 if (this._debug) {
michael@0 358 this.LOG("Could not create nsIPaymentUIGlue instance");
michael@0 359 }
michael@0 360 this.paymentFailed(aRequestId,
michael@0 361 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
michael@0 362 return false;
michael@0 363 }
michael@0 364 glue.showPaymentFlow(aRequestId,
michael@0 365 paymentFlowInfo,
michael@0 366 this.paymentFailed.bind(this));
michael@0 367 },
michael@0 368
michael@0 369 // nsIObserver
michael@0 370
michael@0 371 observe: function observe(subject, topic, data) {
michael@0 372 if (topic == "xpcom-shutdown") {
michael@0 373 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
michael@0 374 ppmm.removeMessageListener(msgname, this);
michael@0 375 }
michael@0 376 this.registeredProviders = null;
michael@0 377 this.messageManagers = null;
michael@0 378
michael@0 379 Services.obs.removeObserver(this, "xpcom-shutdown");
michael@0 380 }
michael@0 381 },
michael@0 382
michael@0 383 LOG: function LOG(s) {
michael@0 384 if (!this._debug) {
michael@0 385 return;
michael@0 386 }
michael@0 387 dump("-*- PaymentManager: " + s + "\n");
michael@0 388 }
michael@0 389 };
michael@0 390
michael@0 391 PaymentManager.init();

mercurial