dom/payment/Payment.jsm

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial