dom/identity/tests/mochitest/test_declareAudience.html

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 <!DOCTYPE HTML>
     2 <html>
     3 <!--
     4   https://bugzilla.mozilla.org/show_bug.cgi?id=947374
     5 -->
     6 <head>
     7   <meta charset="utf-8">
     8   <title>Certified apps can changed the default audience of an assertion -- Bug 947374</title>
     9   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
    10   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
    11 </head>
    12 <body>
    13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=947374">Mozilla Bug 947374</a>
    14 <p id="display"></p>
    15 <div id="content">
    17 </div>
    18 <pre id="test">
    19 <script type="application/javascript;version=1.8">
    21 SimpleTest.waitForExplicitFinish();
    23 Components.utils.import("resource://gre/modules/Promise.jsm");
    24 Components.utils.import("resource://gre/modules/Services.jsm");
    25 Components.utils.import("resource://gre/modules/identity/jwcrypto.jsm");
    26 Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm");
    28 // quick check to make sure we can test apps:
    29 is("appStatus" in document.nodePrincipal, true,
    30    "appStatus should be present in nsIPrincipal, if not the rest of this test will fail");
    32 // Mock the Firefox Accounts manager to generate a keypair and provide a fake
    33 // cert for the caller on each getAssertion request.
    34 function MockFXAManager() {}
    36 MockFXAManager.prototype = {
    37   getAssertion: function(audience, options) {
    38     // Always reject a request for a silent assertion, simulating the
    39     // scenario in which there is no signed-in user to begin with.
    40     if (options.silent) {
    41       return Promise.resolve(null);
    42     }
    44     let deferred = Promise.defer();
    45     jwcrypto.generateKeyPair("DS160", (err, kp) => {
    46       if (err) {
    47         return deferred.reject(err);
    48       }
    49       jwcrypto.generateAssertion("fake-cert", kp, audience, (err, assertion) => {
    50         if (err) {
    51           return deferred.reject(err);
    52         }
    53         return deferred.resolve(assertion);
    54       });
    55     });
    56     return deferred.promise;
    57   }
    58 };
    60 let originalManager = FirefoxAccounts.fxAccountsManager;
    61 FirefoxAccounts.fxAccountsManager = new MockFXAManager();
    63 // The manifests for these apps are all declared in
    64 // /testing/profiles/webapps_mochitest.json.  They are injected into the profile
    65 // by /testing/mochitest/runtests.py with the appropriate appStatus.  So we don't
    66 // have to manually install any apps.
    67 //
    68 // For each app, we will use the file_declareAudience.html content to populate an
    69 // iframe.  The iframe will request() a firefox accounts assertion.  It will then
    70 // postMessage the results of this experiment back down to us, and we will
    71 // compare it with the expected results.
    72 let apps = [
    73   {
    74     title: "an installed app, which should neither be able to use FxA, nor change audience",
    75     manifest: "https://example.com/manifest.webapp",
    76     appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED,
    77     origin: "https://example.com",
    78     wantAudience: "https://i-cant-have-this.com",
    79     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
    80     expected: {
    81       success: false,
    82       underprivileged: true,
    83     },
    84   },
    85   {
    86     title: "an app's assertion audience should be its origin by default",
    87     manifest: "https://example.com/manifest_priv.webapp",
    88     appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
    89     origin: "https://example.com",
    90     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
    91     expected: {
    92       success: true,
    93       underprivileged: false,
    94     },
    95   },
    96   {
    97     title: "a privileged app, which may not have an audience other than its origin",
    98     manifest: "https://example.com/manifest_priv.webapp",
    99     appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
   100     origin: "https://example.com",
   101     wantAudience: "https://i-like-pie.com",
   102     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
   103     expected: {
   104       success: false,
   105       underprivileged: false,
   106     },
   107   },
   108   {
   109     title: "a privileged app, which may declare an audience the same as its origin",
   110     manifest: "https://example.com/manifest_priv.webapp",
   111     appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
   112     origin: "https://example.com",
   113     wantAudience: "https://example.com",
   114     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
   115     expected: {
   116       success: true,
   117     },
   118   },
   119   {
   120     title: "a certified app, which may do whatever it damn well pleases",
   121     manifest: "https://example.com/manifest_cert.webapp",
   122     appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED,
   123     origin: "https://example.com",
   124     wantAudience: "https://whatever-i-want.com",
   125     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
   126     expected: {
   127       success: true,
   128     },
   129   },
   130 ];
   132 let appIndex = 0;
   133 let expectedErrors = 0;
   134 let receivedErrors = [];
   135 let testRunner = runTest();
   137 // Successful tests will send exactly one message.  But for error tests, we may
   138 // have more than one message from the onerror handler in the client.  So we keep
   139 // track of received errors; once they reach the expected count, we are done.
   140 function receiveMessage(event) {
   141   let result = JSON.parse(event.data);
   142   let app = apps[appIndex];
   143   let expected = app.expected;
   145   is(result.success, expected.success,
   146      "Assertion request succeeds");
   148   if (expected.success) {
   149     // Confirm that the assertion audience and origin are as expected
   150     let components = extractAssertionComponents(result.backedAssertion);
   151     is(components.payload.aud, app.wantAudience || app.origin,
   152        "Got desired assertion audience");
   154   } else {
   155     receivedErrors.push(result.error);
   156   }
   158   if (receivedErrors.length === expectedErrors) {
   160     if (expected.underprivileged) {
   161       ok(receivedErrors.indexOf("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS") > -1,
   162          "Expect a complaint that this app cannot use FxA.");
   163     }
   164     if (!expected.success) {
   165       ok(receivedErrors.indexOf("ERROR_INVALID_ASSERTION_AUDIENCE") > -1,
   166          "Expect an error getting an assertion");
   167     }
   169     appIndex += 1;
   171     if (appIndex === apps.length) {
   172       window.removeEventListener("message", receiveMessage);
   174       FirefoxAccounts.fxAccountsManager = originalManager;
   176       SimpleTest.finish();
   177       return;
   178     }
   180     testRunner.next();
   181   }
   182 }
   184 window.addEventListener("message", receiveMessage, false, true);
   186 function runTest() {
   187   for (let app of apps) {
   188     dump("** Testing " + app.title + "\n");
   189     // Set up state for message handler
   190     expectedErrors = 0;
   191     receivedErrors = [];
   192     if (!app.expected.success) {
   193       expectedErrors += 1;
   194     }
   195     if (app.expected.underprivileged) {
   196       expectedErrors += 1;
   197     }
   199     let iframe = document.createElement("iframe");
   201     iframe.setAttribute("mozapp", app.manifest);
   202     iframe.setAttribute("mozbrowser", "true");
   203     iframe.src = app.uri;
   205     document.getElementById("content").appendChild(iframe);
   207     iframe.addEventListener("load", function onLoad() {
   208       iframe.removeEventListener("load", onLoad);
   210       let principal = iframe.contentDocument.nodePrincipal;
   211       is(principal.appStatus, app.appStatus,
   212          "Iframe's document.nodePrincipal has expected appStatus");
   214       // Because the <iframe mozapp> can't parent its way back to us, we
   215       // provide this handle to our window so it can postMessage to us.
   216       iframe.contentWindow.wrappedJSObject.realParent = window;
   218       // Test what we want to test, viz. whether or not the app can request
   219       // an assertion with an audience the same as or different from its
   220       // origin.  The client will post back its success or failure in procuring
   221       // an identity assertion from Firefox Accounts.
   222       iframe.contentWindow.postMessage({audience: app.wantAudience}, "*");
   223     }, false);
   225     yield undefined;
   226   }
   227 }
   229 function extractAssertionComponents(backedAssertion) {
   230   let [_, signedObject] = backedAssertion.split("~");
   231   let parts = signedObject.split(".");
   233   let headerSegment = parts[0];
   234   let payloadSegment = parts[1];
   235   let cryptoSegment = parts[2];
   237   let header = JSON.parse(base64UrlDecode(headerSegment));
   238   let payload = JSON.parse(base64UrlDecode(payloadSegment));
   240   return {header: header,
   241           payload: payload,
   242           headerSegment: headerSegment,
   243           payloadSegment: payloadSegment,
   244           cryptoSegment: cryptoSegment};
   245 };
   247 function base64UrlDecode(s) {
   248   s = s.replace(/-/g, "+");
   249   s = s.replace(/_/g, "/");
   250   // Don't need to worry about reintroducing padding ('=='), since
   251   // jwcrypto provides that.
   252   return atob(s);
   253 }
   255 SpecialPowers.pushPrefEnv({"set":
   256   [
   257     ["dom.mozBrowserFramesEnabled", true],
   258     ["dom.identity.enabled", true],
   259     ["identity.fxaccounts.enabled", true],
   260     ["toolkit.identity.debug", true],
   261     ["dom.identity.syntheticEventsOk", true],
   263     ["security.apps.privileged.CSP.default", ""],
   264     ["security.apps.certified.CSP.default", ""],
   265   ]},
   266   function() {
   267     testRunner.next();
   268   }
   269 );
   272 </script>
   273 </pre>
   274 </body>
   275 </html>

mercurial