<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
  <script class="testbody" type="application/javascript">
"use strict";
var { IdpSandbox } = SpecialPowers.ChromeUtils.import(
  "resource://gre/modules/media/IdpSandbox.jsm"
);
var dummyPayload = JSON.stringify({
  this: 'is',
  a: ['stu', 6],
  obj: null
});

function test_domain_sandbox() {
  var diabolical = {
    toString() {
      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, undefined, window);
      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, window);
      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, window);
  return sandbox.start().then(idp => SpecialPowers.wrap(idp));
}

function test_generate_assertion() {
  return makeSandbox()
    .then(idp => idp.generateAssertion(dummyPayload,
                                       'https://example.net',
                                        {}))
    .then(response => {
      response = SpecialPowers.wrap(response);
      is(response.idp.domain, 'example.com', 'domain is correct');
      is(response.idp.protocol, 'idp.js', 'protocol is correct');
      ok(typeof response.assertion === 'string', 'assertion is present');
    });
}

// test that the test IdP can eat its own dogfood; which is the only way to test
// validateAssertion, since that consumes the output of generateAssertion (in
// theory, generateAssertion could identify a different IdP domain).

function test_validate_assertion() {
  return makeSandbox()
    .then(idp => idp.generateAssertion(dummyPayload,
                                       'https://example.net',
                                       { usernameHint: 'user' }))
    .then(assertion => {
      var wrapped = SpecialPowers.wrap(assertion);
      return makeSandbox()
        .then(idp => idp.validateAssertion(wrapped.assertion,
                                           'https://example.net'));
    }).then(response => {
      response = SpecialPowers.wrap(response);
      is(response.identity, 'user@example.com');
      is(response.contents, dummyPayload);
    });
}

// We don't want to test the #bad or the #hang instructions,
// errors of the sort those generate aren't handled by the sandbox code.
function test_assertion_failure(reason) {
  return () => {
    return makeSandbox(idpName(reason))
      .then(idp => idp.generateAssertion('hello', 'example.net', {}))
      .then(r => ok(false, 'should not succeed on ' + reason),
            e => ok(true, 'failed correctly on ' + reason));
  };
}

function test_load_failure() {
  return makeSandbox('non-existent-file')
    .then(() => ok(false, 'Should fail to load non-existent file'),
          e => ok(e, 'Should fail to load non-existent file'));
}

function test_redirect_ok(from) {
  return () => {
    return makeSandbox(from)
      .then(idp => idp.generateAssertion('hello', 'example.net'))
      .then(r => ok(SpecialPowers.wrap(r).assertion,
                    'Redirect to https should be OK'));
  };
}

function test_redirect_fail(from) {
  return () => {
    return makeSandbox(from)
      .then(() => ok(false, 'Redirect to https should fail'),
            e => ok(e, 'Redirect to https should fail'));
  };
}

function test_bad_js() {
  return makeSandbox('idp-bad.js')
    .then(() => ok(false, 'Bad JS should not load'),
          e => ok(e, 'Bad JS should not load'));
}

function run_all_tests() {
  [
    test_domain_sandbox,
    test_protocol_sandbox,
    test_generate_assertion,
    test_validate_assertion,

    // fail of the IdP fails
    test_assertion_failure('fail'),
    // fail if the IdP throws
    test_assertion_failure('throw'),
    // fail if the IdP is not ready
    test_assertion_failure('not_ready'),

    test_load_failure(),
    // Test a redirect to an HTTPS origin, which should be OK
    test_redirect_ok('idp-redirect-https.js'),
    // Two redirects is fine too
    test_redirect_ok('idp-redirect-https-double.js'),
    // A secure redirect to a path other than /.well-known/idp-proxy/* should
    // also work fine.
    test_redirect_ok('idp-redirect-https-odd-path.js'),
    // A redirect to HTTP is not-cool
    test_redirect_fail('idp-redirect-http.js'),
    // Also catch tricks like https->http->https
    test_redirect_fail('idp-redirect-http-trick.js'),

    test_bad_js
  ].reduce((p, test) => {
    return p.then(test)
      .catch(e => ok(false, test.name + ' failed: ' +
                     SpecialPowers.wrap(e).message + '\n' +
                     SpecialPowers.wrap(e).stack));
  }, Promise.resolve())
    .then(() => SimpleTest.finish());
}

SimpleTest.waitForExplicitFinish();
run_all_tests();
</script>
  </body>
</html>
