Bug 1153314 - Test IdP login flow, r=jib
authorMartin Thomson <martin.thomson@gmail.com>
Wed, 15 Apr 2015 14:19:21 -0700
changeset 258328 bbfa03945585e60ac1bafee72190562b6acba697
parent 258327 f23689de62164560d97bb297a2e5247bc4f251ab
child 258329 42ed856b37d93aedc448585c50fc89dcb063be10
push id8007
push userraliiev@mozilla.com
push dateMon, 11 May 2015 19:23:16 +0000
treeherdermozilla-aurora@e2ce1aac996e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib
bugs1153314
milestone40.0a1
Bug 1153314 - Test IdP login flow, r=jib
dom/media/tests/mochitest/identity/idp.js
dom/media/tests/mochitest/identity/idp.sjs
dom/media/tests/mochitest/identity/login.html
dom/media/tests/mochitest/identity/mochitest.ini
dom/media/tests/mochitest/identity/test_fingerprints.html
dom/media/tests/mochitest/identity/test_getIdentityAssertion.html
dom/media/tests/mochitest/identity/test_idpproxy.html
dom/media/tests/mochitest/identity/test_loginNeeded.html
--- a/dom/media/tests/mochitest/identity/idp.js
+++ b/dom/media/tests/mochitest/identity/idp.js
@@ -1,90 +1,110 @@
 (function(global) {
-  "use strict";
+  'use strict';
 
   // rather than create a million different IdP configurations and litter the
   // world with files all containing near-identical code, let's use the hash/URL
   // fragment as a way of generating instructions for the IdP
-  var instructions = global.location.hash.replace("#", "").split(":");
+  var instructions = global.location.hash.replace('#', '').split(':');
   function is(target) {
     return function(instruction) {
       return instruction === target;
     };
   }
 
   function IDPJS() {
     this.domain = global.location.host;
     var path = global.location.pathname;
     this.protocol =
       path.substring(path.lastIndexOf('/') + 1) + global.location.hash;
+    this.id = crypto.getRandomValues(new Uint8Array(10)).join('.');
   }
 
-  function borkResult(result) {
-    if (instructions.some(is("throw"))) {
-      throw new Error('Throwing!');
-    }
-    if (instructions.some(is("fail"))) {
-      return Promise.reject(new Error('Failing!'));
-    }
-    if (instructions.some(is("loginerror"))) {
-      return Promise.reject({
-        name: 'IdpLoginError',
-        loginUrl: 'https://example.com/log/in/here'
+  IDPJS.prototype = {
+    getLogin: function() {
+      var xhr = new XMLHttpRequest();
+      xhr.open('GET', 'https://example.com/.well-known/idp-proxy/idp.sjs?' + this.id);
+      return new Promise(resolve => {
+        xhr.onload = e => resolve(xhr.status === 200);
+        xhr.send();
       });
-    }
-    if (instructions.some(is("hang"))) {
-      return new Promise(r => {});
-    }
-    dump('idp: result=' + JSON.stringify(result) + '\n');
-    return Promise.resolve(result);
-  };
+    },
+    checkLogin: function(result) {
+      return this.getLogin()
+        .then(loggedIn => {
+          if (loggedIn) {
+            return result;
+          }
+          return Promise.reject({
+            name: 'IdpLoginError',
+            loginUrl: 'https://example.com/.well-known/idp-proxy/login.html#' +
+              this.id
+          });
+        });
+    },
 
-  IDPJS.prototype = {
+    borkResult: function(result) {
+      if (instructions.some(is('throw'))) {
+        throw new Error('Throwing!');
+      }
+      if (instructions.some(is('fail'))) {
+        return Promise.reject(new Error('Failing!'));
+      }
+      if (instructions.some(is('login'))) {
+        return this.checkLogin(result);
+      }
+      if (instructions.some(is('hang'))) {
+        return new Promise(r => {});
+      }
+      dump('idp: result=' + JSON.stringify(result) + '\n');
+      return Promise.resolve(result);
+    },
+
     _selectUsername: function(usernameHint) {
-      var username = "someone@" + this.domain;
+      var username = 'someone@' + this.domain;
       if (usernameHint) {
-        var at = usernameHint.indexOf("@");
+        var at = usernameHint.indexOf('@');
         if (at < 0) {
-          username = usernameHint + "@" + this.domain;
+          username = usernameHint + '@' + this.domain;
         } else if (usernameHint.substring(at + 1) === this.domain) {
           username = usernameHint;
         }
       }
       return username;
     },
 
     generateAssertion: function(payload, origin, usernameHint) {
       dump('idp: generateAssertion(' + payload + ')\n');
       var idpDetails = {
         domain: this.domain,
         protocol: this.protocol
       };
-      if (instructions.some(is("bad-assert"))) {
+      if (instructions.some(is('bad-assert'))) {
         idpDetails = {};
       }
-      return borkResult({
+      return this.borkResult({
         idp: idpDetails,
         assertion: JSON.stringify({
           username: this._selectUsername(usernameHint),
           contents: payload
         })
       });
     },
 
     validateAssertion: function(assertion, origin) {
       dump('idp: validateAssertion(' + assertion + ')\n');
       var assertion = JSON.parse(assertion);
-      if (instructions.some(is("bad-validate"))) {
+      if (instructions.some(is('bad-validate'))) {
         assertion.contents = {};
       }
-      return borkResult({
+      return this.borkResult({
           identity: assertion.username,
           contents: assertion.contents
         });
     }
   };
 
-  if (!instructions.some(is("not_ready"))) {
+  if (!instructions.some(is('not_ready'))) {
     dump('registering idp.js' + global.location.hash + '\n');
     global.rtcIdentityProvider.register(new IDPJS());
   }
 }(this));
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/identity/idp.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+  var key = '/.well-known/idp-proxy/' + request.queryString;
+  dump(getState(key) + '\n');
+  if (request.method === 'GET') {
+    if (getState(key)) {
+      response.setStatusLine(request.httpVersion, 200, 'OK');
+    } else {
+      response.setStatusLine(request.httpVersion, 404, 'Not Found');
+    }
+  } else if (request.method === 'PUT') {
+    setState(key, 'OK');
+    response.setStatusLine(request.httpVersion, 200, 'OK');
+  } else {
+    response.setStatusLine(request.httpVersion, 406, 'Method Not Allowed');
+  }
+  response.setHeader('Content-Type', 'text/plain;charset=UTF-8');
+  response.write('OK');
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/identity/login.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Identity Provider Login</title>
+  <script type="application/javascript">
+  window.onload = () => {
+    var xhr = new XMLHttpRequest();
+    xhr.open("PUT", "https://example.com/.well-known/idp-proxy/idp.sjs?" +
+             window.location.hash.replace('#', ''));
+    xhr.onload = () => {
+      var isFramed = (window !== window.top);
+      var parent = isFramed ? window.parent : window.opener;
+      // Using '*' is cheating, but that's OK.
+      parent.postMessage('LOGINDONE', '*');
+      var done = document.createElement('div');
+
+      done.textContent = 'Done';
+      document.body.appendChild(done);
+
+      if (!isFramed) {
+        window.close();
+      }
+    };
+    xhr.send();
+  };
+  </script>
+</head>
+<body>
+  <div>Logging in...</div>
+</body>
+</html>
--- a/dom/media/tests/mochitest/identity/mochitest.ini
+++ b/dom/media/tests/mochitest/identity/mochitest.ini
@@ -22,11 +22,16 @@ support-files =
   /.well-known/idp-min.js
 
 [test_fingerprints.html]
 [test_getIdentityAssertion.html]
 [test_setIdentityProvider.html]
 [test_setIdentityProviderWithErrors.html]
 [test_peerConnection_peerIdentity.html]
 [test_peerConnection_asymmetricIsolation.html]
+[test_loginNeeded.html]
+support-files =
+  /.well-known/idp-proxy/login.html
+  /.well-known/idp-proxy/idp.sjs
+
 
 # Bug 950317: Hack for making a cleanup hook after finishing all WebRTC cases
 [../test_zmedia_cleanup.html]
--- a/dom/media/tests/mochitest/identity/test_fingerprints.html
+++ b/dom/media/tests/mochitest/identity/test_fingerprints.html
@@ -7,17 +7,17 @@
 <body>
   <script class="testbody" type="application/javascript">
 'use strict';
 
 // here we call the identity provider directly
 function getIdentityAssertion(fpArray) {
   var Cu = SpecialPowers.Cu;
   var rtcid = Cu.import('resource://gre/modules/media/IdpSandbox.jsm');
-  var sandbox = new rtcid.IdpSandbox('example.com', 'idp.js');
+  var sandbox = new rtcid.IdpSandbox('example.com', 'idp.js', window.document);
   return sandbox.start()
     .then(idp => SpecialPowers.wrap(idp)
                    .generateAssertion(JSON.stringify({ fingerprint: fpArray }),
                                       'https://example.com'))
     .then(assertion => {
       assertion = SpecialPowers.wrap(assertion);
       var assertionString = btoa(JSON.stringify(assertion));
       sandbox.stop();
--- a/dom/media/tests/mochitest/identity/test_getIdentityAssertion.html
+++ b/dom/media/tests/mochitest/identity/test_getIdentityAssertion.html
@@ -50,21 +50,22 @@ function theTest() {
 
     function PC_LOCAL_IDP_FAILS(t) {
       return getAssertion(t, '#fail')
         .then(a => ok(false, '#fail should not get an identity result'),
               e => is(e.name, 'IdpError', '#fail should cause rejection'));
     },
 
     function PC_LOCAL_IDP_LOGIN_ERROR(t) {
-      return getAssertion(t, '#loginerror')
-        .then(a => ok(false, '#loginerror should not work'),
+      return getAssertion(t, '#login')
+        .then(a => ok(false, '#login should not work'),
               e => {
                 is(e.name, 'IdpLoginError', 'name is IdpLoginError');
-                is(t.pcLocal._pc.idpLoginUrl, 'https://example.com/log/in/here',
+                is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
+                   'https://example.com/.well-known/idp-proxy/login.html',
                    'got the right login URL from the IdP');
               });
     },
 
     function PC_LOCAL_IDP_NOT_READY(t) {
       return getAssertion(t, '#not_ready')
         .then(a => ok(false, '#not_ready should not get an identity result'),
               e => is(e.name, 'IdpError', '#not_ready should cause rejection'));
--- a/dom/media/tests/mochitest/identity/test_idpproxy.html
+++ b/dom/media/tests/mochitest/identity/test_idpproxy.html
@@ -21,47 +21,47 @@ function test_domain_sandbox() {
     toString : function() {
       return 'example.com/path';
     }
   };
   var domains = [ 'ex/foo', 'user@ex', 'user:pass@ex', 'ex#foo', 'ex?foo',
                   '', 12, null, diabolical, true ];
   domains.forEach(function(domain) {
     try {
-      var idp = new IdpSandbox(domain);
+      var idp = new IdpSandbox(domain, undefined, window.document);
       ok(false, 'IdpSandbox allowed a bad domain: ' + domain);
     } catch (e) {
       var str = (typeof domain === 'string') ? domain : typeof domain;
       ok(true, 'Evil domain "' + str + '" raises exception');
     }
   });
 }
 
 function test_protocol_sandbox() {
   var protos = [ '../evil/proto', '..%2Fevil%2Fproto',
                  '\\evil', '%5cevil', 12, true, {} ];
   protos.forEach(function(proto) {
     try {
-      var idp = new IdpSandbox('example.com', proto);
+      var idp = new IdpSandbox('example.com', proto, window.document);
       ok(false, 'IdpSandbox allowed a bad protocol: ' + proto);
     } catch (e) {
       var str = (typeof proto === 'string') ? proto : typeof proto;
       ok(true, 'Evil protocol "' + proto + '" raises exception');
     }
   });
 }
 
 function idpName(hash) {
   return 'idp.js' + (hash ? ('#' + hash) : '');
 }
 
 function makeSandbox(js) {
   var name = js || idpName();
   info('Creating a sandbox for the protocol: ' + name);
-  var sandbox = new IdpSandbox('example.com', name);
+  var sandbox = new IdpSandbox('example.com', name, window.document);
   return sandbox.start().then(idp => SpecialPowers.wrap(idp));
 }
 
 function test_generate_assertion() {
   return makeSandbox()
     .then(idp => idp.generateAssertion(dummyPayload,
                                        'https://example.net'))
     .then(response => {
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/identity/test_loginNeeded.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript">var scriptRelativePath = "../";</script>
+  <script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    title: 'RTCPeerConnection identity with login',
+    bug: '1153314'
+  });
+
+function waitForLoginDone() {
+  return new Promise(resolve => {
+    window.addEventListener('message', function listener(e) {
+      is(e.origin, 'https://example.com', 'got the right message origin');
+      is(e.data, 'LOGINDONE', 'got the right message');
+      window.removeEventListener('message', listener);
+      resolve();
+    }, false);
+  });
+}
+
+function checkLogin(t, name, onLoginNeeded) {
+  t.pcLocal.setIdentityProvider('example.com', 'idp.js#login:' + name);
+  return t.pcLocal._pc.getIdentityAssertion()
+    .then(a => ok(false, 'should request login'),
+          e => {
+            is(e.name, 'IdpLoginError', 'name is IdpLoginError');
+            is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
+               'https://example.com/.well-known/idp-proxy/login.html',
+               'got the right login URL from the IdP');
+            return t.pcLocal._pc.idpLoginUrl;
+          })
+    .then(onLoginNeeded)
+    .then(waitForLoginDone)
+    .then(() => t.pcLocal._pc.getIdentityAssertion())
+    .then(a => ok(a, 'got assertion'));
+}
+
+function theTest() {
+  var test = new PeerConnectionTest();
+  test.setMediaConstraints([{audio: true}], [{audio: true}]);
+  test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE');
+  test.chain.append([
+    function PC_LOCAL_IDENTITY_ASSERTION_WITH_IFRAME_LOGIN(t) {
+      return checkLogin(t, 'iframe', loginUrl => {
+        var iframe = document.createElement('iframe');
+        iframe.setAttribute('src', loginUrl);
+        iframe.frameBorder = 0;
+        iframe.width = 400;
+        iframe.height = 60;
+        document.getElementById('display').appendChild(iframe);
+      });
+    },
+    function PC_LOCAL_IDENTITY_ASSERTION_WITH_WINDOW_LOGIN(t) {
+      return checkLogin(t, 'openwin', loginUrl => {
+        window.open(loginUrl, 'login', 'width=400,height=60');
+      });
+    }
+  ]);
+  test.run();
+}
+runNetworkTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>