dom/media/IdpProxy.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.

     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
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = ["IdpProxy"];
     9 const {
    10   classes: Cc,
    11   interfaces: Ci,
    12   utils: Cu,
    13   results: Cr
    14 } = Components;
    16 Cu.import("resource://gre/modules/Services.jsm");
    17 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    19 XPCOMUtils.defineLazyModuleGetter(this, "Sandbox",
    20                                   "resource://gre/modules/identity/Sandbox.jsm");
    22 /**
    23  * An invisible iframe for hosting the idp shim.
    24  *
    25  * There is no visible UX here, as we assume the user has already
    26  * logged in elsewhere (on a different screen in the web site hosting
    27  * the RTC functions).
    28  */
    29 function IdpChannel(uri, messageCallback) {
    30   this.sandbox = null;
    31   this.messagechannel = null;
    32   this.source = uri;
    33   this.messageCallback = messageCallback;
    34 }
    36 IdpChannel.prototype = {
    37   /**
    38    * Create a hidden, sandboxed iframe for hosting the IdP's js shim.
    39    *
    40    * @param callback
    41    *                (function) invoked when this completes, with an error
    42    *                argument if there is a problem, no argument if everything is
    43    *                ok
    44    */
    45   open: function(callback) {
    46     if (this.sandbox) {
    47       return callback(new Error("IdP channel already open"));
    48     }
    50     let ready = this._sandboxReady.bind(this, callback);
    51     this.sandbox = new Sandbox(this.source, ready);
    52   },
    54   _sandboxReady: function(aCallback, aSandbox) {
    55     // Inject a message channel into the subframe.
    56     try {
    57       this.messagechannel = new aSandbox._frame.contentWindow.MessageChannel();
    58       Object.defineProperty(
    59         aSandbox._frame.contentWindow.wrappedJSObject,
    60         "rtcwebIdentityPort",
    61         {
    62           value: this.messagechannel.port2
    63         }
    64       );
    65     } catch (e) {
    66       this.close();
    67       aCallback(e); // oops, the IdP proxy overwrote this.. bad
    68       return;
    69     }
    70     this.messagechannel.port1.onmessage = function(msg) {
    71       this.messageCallback(msg.data);
    72     }.bind(this);
    73     this.messagechannel.port1.start();
    74     aCallback();
    75   },
    77   send: function(msg) {
    78     this.messagechannel.port1.postMessage(msg);
    79   },
    81   close: function IdpChannel_close() {
    82     if (this.sandbox) {
    83       if (this.messagechannel) {
    84         this.messagechannel.port1.close();
    85       }
    86       this.sandbox.free();
    87     }
    88     this.messagechannel = null;
    89     this.sandbox = null;
    90   }
    91 };
    93 /**
    94  * A message channel between the RTC PeerConnection and a designated IdP Proxy.
    95  *
    96  * @param domain (string) the domain to load up
    97  * @param protocol (string) Optional string for the IdP protocol
    98  */
    99 function IdpProxy(domain, protocol) {
   100   IdpProxy.validateDomain(domain);
   101   IdpProxy.validateProtocol(protocol);
   103   this.domain = domain;
   104   this.protocol = protocol || "default";
   106   this._reset();
   107 }
   109 /**
   110  * Checks that the domain is only a domain, and doesn't contain anything else.
   111  * Adds it to a URI, then checks that it matches perfectly.
   112  */
   113 IdpProxy.validateDomain = function(domain) {
   114   let message = "Invalid domain for identity provider; ";
   115   if (!domain || typeof domain !== "string") {
   116     throw new Error(message + "must be a non-zero length string");
   117   }
   119   message += "must only have a domain name and optionally a port";
   120   try {
   121     let ioService = Components.classes["@mozilla.org/network/io-service;1"]
   122                     .getService(Components.interfaces.nsIIOService);
   123     let uri = ioService.newURI('https://' + domain + '/', null, null);
   125     // this should trap errors
   126     // we could check uri.userPass, uri.path and uri.ref, but there is no need
   127     if (uri.hostPort !== domain) {
   128       throw new Error(message);
   129     }
   130   } catch (e if (e.result === Cr.NS_ERROR_MALFORMED_URI)) {
   131     throw new Error(message);
   132   }
   133 };
   135 /**
   136  * Checks that the IdP protocol is sane.  In particular, we don't want someone
   137  * adding relative paths (e.g., "../../myuri"), which could be used to move
   138  * outside of /.well-known/ and into space that they control.
   139  */
   140 IdpProxy.validateProtocol = function(protocol) {
   141   if (!protocol) {
   142     return;  // falsy values turn into "default", so they are OK
   143   }
   144   let message = "Invalid protocol for identity provider; ";
   145   if (typeof protocol !== "string") {
   146     throw new Error(message + "must be a string");
   147   }
   148   if (decodeURIComponent(protocol).match(/[\/\\]/)) {
   149     throw new Error(message + "must not include '/' or '\\'");
   150   }
   151 };
   153 IdpProxy.prototype = {
   154   _reset: function() {
   155     this.channel = null;
   156     this.ready = false;
   158     this.counter = 0;
   159     this.tracking = {};
   160     this.pending = [];
   161   },
   163   isSame: function(domain, protocol) {
   164     return this.domain === domain && ((protocol || "default") === this.protocol);
   165   },
   167   /**
   168    * Get a sandboxed iframe for hosting the idp-proxy's js. Create a message
   169    * channel down to the frame.
   170    *
   171    * @param errorCallback (function) a callback that will be invoked if there
   172    *                is a fatal error starting the proxy
   173    */
   174   start: function(errorCallback) {
   175     if (this.channel) {
   176       return;
   177     }
   178     let well_known = "https://" + this.domain;
   179     well_known += "/.well-known/idp-proxy/" + this.protocol;
   180     this.channel = new IdpChannel(well_known, this._messageReceived.bind(this));
   181     this.channel.open(function(error) {
   182       if (error) {
   183         this.close();
   184         if (typeof errorCallback === "function") {
   185           errorCallback(error);
   186         }
   187       }
   188     }.bind(this));
   189   },
   191   /**
   192    * Send a message up to the idp proxy. This should be an RTC "SIGN" or
   193    * "VERIFY" message. This method adds the tracking 'id' parameter
   194    * automatically to the message so that the callback is only invoked for the
   195    * response to the message.
   196    *
   197    * This enqueues the message to send if the IdP hasn't signaled that it is
   198    * "READY", and sends the message when it is.
   199    *
   200    * The caller is responsible for ensuring that a response is received. If the
   201    * IdP doesn't respond, the callback simply isn't invoked.
   202    */
   203   send: function(message, callback) {
   204     this.start();
   205     if (this.ready) {
   206       message.id = "" + (++this.counter);
   207       this.tracking[message.id] = callback;
   208       this.channel.send(message);
   209     } else {
   210       this.pending.push({ message: message, callback: callback });
   211     }
   212   },
   214   /**
   215    * Handle a message from the IdP. This automatically sends if the message is
   216    * 'READY' so there is no need to track readiness state outside of this obj.
   217    */
   218   _messageReceived: function(message) {
   219     if (!message) {
   220       return;
   221     }
   222     if (!this.ready && message.type === "READY") {
   223       this.ready = true;
   224       this.pending.forEach(function(p) {
   225         this.send(p.message, p.callback);
   226       }, this);
   227       this.pending = [];
   228     } else if (this.tracking[message.id]) {
   229       var callback = this.tracking[message.id];
   230       delete this.tracking[message.id];
   231       callback(message);
   232     } else {
   233       let console = Cc["@mozilla.org/consoleservice;1"].
   234         getService(Ci.nsIConsoleService);
   235       console.logStringMessage("Received bad message from IdP: " +
   236                                message.id + ":" + message.type);
   237     }
   238   },
   240   /**
   241    * Performs cleanup.  The object should be OK to use again.
   242    */
   243   close: function() {
   244     if (!this.channel) {
   245       return;
   246     }
   248     // clear out before letting others know in case they do something bad
   249     let trackingCopy = this.tracking;
   250     let pendingCopy = this.pending;
   252     this.channel.close();
   253     this._reset();
   255     // dump a message of type "ERROR" in response to all outstanding
   256     // messages to the IdP
   257     let error = { type: "ERROR", error: "IdP closed" };
   258     Object.keys(trackingCopy).forEach(function(k) {
   259       trackingCopy[k](error);
   260     });
   261     pendingCopy.forEach(function(p) {
   262       p.callback(error);
   263     });
   264   },
   266   toString: function() {
   267     return this.domain + '/.../' + this.protocol;
   268   }
   269 };
   271 this.IdpProxy = IdpProxy;

mercurial