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.

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

mercurial