tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RTCPeerConnection-getIdentityAssertion.sub.https.html (15072B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection.prototype.getIdentityAssertion</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="identity-helper.sub.js"></script>
      7 <script>
      8  'use strict';
      9 
     10  // Test is based on the following editor draft:
     11  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     12 
     13  // The tests here interacts with the mock identity provider located at
     14  //   /.well-known/idp-proxy/mock-idp.js
     15 
     16  // The following helper functions are called from identity-helper.sub.js
     17  //   parseAssertionResult
     18  //   getIdpDomains
     19  //   assert_rtcerror_rejection
     20  //   hostString
     21 
     22  /*
     23    9.6.  RTCPeerConnection Interface Extensions
     24      partial interface RTCPeerConnection {
     25        void               setIdentityProvider(DOMString provider,
     26                                               optional RTCIdentityProviderOptions options);
     27        Promise<DOMString> getIdentityAssertion();
     28        readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
     29        readonly attribute DOMString?                    idpLoginUrl;
     30        readonly attribute DOMString?                    idpErrorInfo;
     31      };
     32 
     33      dictionary RTCIdentityProviderOptions {
     34        DOMString protocol = "default";
     35        DOMString usernameHint;
     36        DOMString peerIdentity;
     37      };
     38   */
     39  promise_test(t => {
     40    const pc = new RTCPeerConnection();
     41    const port = window.location.port;
     42 
     43    const [idpDomain] = getIdpDomains();
     44    const idpHost = hostString(idpDomain, port);
     45 
     46    pc.setIdentityProvider(idpHost, {
     47      protocol: 'mock-idp.js?foo=bar',
     48      usernameHint: `alice@${idpDomain}`,
     49      peerIdentity: 'bob@example.org'
     50    });
     51 
     52    return pc.getIdentityAssertion()
     53    .then(assertionResultStr => {
     54      const { idp, assertion } = parseAssertionResult(assertionResultStr);
     55 
     56      assert_equals(idp.domain, idpHost,
     57        'Expect mock-idp.js to construct domain from its location.host');
     58 
     59      assert_equals(idp.protocol, 'mock-idp.js',
     60        'Expect mock-idp.js to return protocol of itself with no query string');
     61 
     62      const {
     63        watermark,
     64        args,
     65        env,
     66        query,
     67      } = assertion;
     68 
     69      assert_equals(watermark, 'mock-idp.js.watermark',
     70        'Expect assertion result to contain watermark left by mock-idp.js');
     71 
     72      assert_equals(args.origin, window.origin,
     73        'Expect args.origin argument to be the origin of this window');
     74 
     75      assert_equals(env.location.href,
     76        `https://${idpHost}/.well-known/idp-proxy/mock-idp.js?foo=bar`,
     77        'Expect IdP proxy to be loaded with full well-known URL constructed from provider and protocol');
     78 
     79      assert_equals(env.location.origin, `https://${idpHost}`,
     80        'Expect IdP to have its own origin');
     81 
     82      assert_equals(args.options.protocol, 'mock-idp.js?foo=bar',
     83        'Expect options.protocol to be the same value as being passed from here');
     84 
     85      assert_equals(args.options.usernameHint, `alice@${idpDomain}`,
     86        'Expect options.usernameHint to be the same value as being passed from here');
     87 
     88      assert_equals(args.options.peerIdentity, 'bob@example.org',
     89        'Expect options.peerIdentity to be the same value as being passed from here');
     90 
     91      assert_equals(query.foo, 'bar',
     92        'Expect query string to be parsed by mock-idp.js and returned back');
     93    });
     94  }, 'getIdentityAssertion() should load IdP proxy and return assertion generated');
     95 
     96  // When generating assertion, the RTCPeerConnection doesn't care if the returned assertion
     97  // represents identity of different domain
     98  promise_test(t => {
     99    const pc = new RTCPeerConnection();
    100    const port = window.location.port;
    101 
    102    const [idpDomain1, idpDomain2] = getIdpDomains();
    103    assert_not_equals(idpDomain1, idpDomain2,
    104      'Sanity check two idpDomains are different');
    105 
    106    // Ask mock-idp.js to return a custom domain idpDomain2 and custom protocol foo
    107    pc.setIdentityProvider(hostString(idpDomain1, port), {
    108      protocol: `mock-idp.js?generatorAction=return-custom-idp&domain=${idpDomain2}&protocol=foo`,
    109      usernameHint: `alice@${idpDomain2}`,
    110    });
    111 
    112    return pc.getIdentityAssertion()
    113    .then(assertionResultStr => {
    114      const { idp, assertion } = parseAssertionResult(assertionResultStr);
    115      assert_equals(idp.domain, idpDomain2);
    116      assert_equals(idp.protocol, 'foo');
    117      assert_equals(assertion.args.options.usernameHint, `alice@${idpDomain2}`);
    118    });
    119  }, 'getIdentityAssertion() should succeed if mock-idp.js return different domain and protocol in assertion');
    120 
    121  /*
    122    9.3.  Requesting Identity Assertions
    123      4.  If the IdP proxy produces an error or returns a promise that does not resolve to
    124          a valid RTCIdentityValidationResult (see 9.5 IdP Error Handling), then identity
    125          validation fails.
    126 
    127    9.5.  IdP Error Handling
    128      - If an identity provider throws an exception or returns a promise that is ultimately
    129        rejected, then the procedure that depends on the IdP MUST also fail. These types of
    130        errors will cause an IdP failure with an RTCError with errorDetail set to
    131        "idp-execution-failure".
    132 
    133    9.6.  RTCPeerConnection Interface Extensions
    134      idpErrorInfo
    135        An attribute that the IdP can use to pass additional information back to the
    136        applications about the error. The format of this string is defined by the IdP and
    137        may be JSON.
    138   */
    139  promise_test(t => {
    140    const pc = new RTCPeerConnection();
    141 
    142    assert_equals(pc.idpErrorInfo, null,
    143      'Expect initial pc.idpErrorInfo to be null');
    144 
    145    const port = window.location.port;
    146    const [idpDomain] = getIdpDomains();
    147 
    148    // Ask mock-idp.js to throw an error with err.errorInfo set to bar
    149    pc.setIdentityProvider(hostString(idpDomain, port), {
    150      protocol: `mock-idp.js?generatorAction=throw-error&errorInfo=bar`,
    151      usernameHint: `alice@${idpDomain}`,
    152    });
    153 
    154    return assert_rtcerror_rejection('idp-execution-failure',
    155      pc.getIdentityAssertion())
    156    .then(() => {
    157      assert_equals(pc.idpErrorInfo, 'bar',
    158        'Expect pc.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js');
    159    });
    160  }, `getIdentityAssertion() should reject with RTCError('idp-execution-failure') if mock-idp.js throws error`);
    161 
    162  /*
    163    9.5.  IdP Error Handling
    164      - If the script loaded from the identity provider is not valid JavaScript or does
    165        not implement the correct interfaces, it causes an IdP failure with an RTCError
    166        with errorDetail set to "idp-bad-script-failure".
    167   */
    168  promise_test(t => {
    169    const pc = new RTCPeerConnection();
    170 
    171    const port = window.location.port;
    172    const [idpDomain] = getIdpDomains();
    173 
    174    // Ask mock-idp.js to not register its callback to the
    175    // RTCIdentityProviderRegistrar
    176    pc.setIdentityProvider(hostString(idpDomain, port), {
    177      protocol: `mock-idp.js?action=do-not-register`,
    178      usernameHint: `alice@${idpDomain}`,
    179    });
    180 
    181    return assert_rtcerror_rejection('idp-bad-script-failure',
    182      pc.getIdentityAssertion());
    183 
    184  }, `getIdentityAssertion() should reject with RTCError('idp-bad-script-failure') if IdP proxy script do not register its callback`);
    185 
    186  /*
    187    9.3.  Requesting Identity Assertions
    188      4.  If the IdP proxy produces an error or returns a promise that does not resolve
    189          to a valid RTCIdentityAssertionResult (see 9.5 IdP Error Handling), then assertion
    190          generation fails.
    191   */
    192  promise_test(t => {
    193    const pc = new RTCPeerConnection();
    194 
    195    const port = window.location.port;
    196    const [idpDomain] = getIdpDomains();
    197 
    198    // Ask mock-idp.js to return an invalid result that is not proper
    199    // RTCIdentityAssertionResult
    200    pc.setIdentityProvider(hostString(idpDomain, port), {
    201      protocol: `mock-idp.js?generatorAction=return-invalid-result`,
    202      usernameHint: `alice@${idpDomain}`,
    203    });
    204 
    205    return promise_rejects_dom(t, 'OperationError',
    206      pc.getIdentityAssertion());
    207  }, `getIdentityAssertion() should reject with OperationError if mock-idp.js return invalid result`);
    208 
    209  /*
    210    9.5.  IdP Error Handling
    211      - A RTCPeerConnection might be configured with an identity provider, but loading of
    212        the IdP URI fails. Any procedure that attempts to invoke such an identity provider
    213        and cannot load the URI fails with an RTCError with errorDetail set to
    214        "idp-load-failure" and the httpRequestStatusCode attribute of the error set to the
    215        HTTP status code of the response.
    216   */
    217  promise_test(t => {
    218    const pc = new RTCPeerConnection();
    219 
    220    pc.setIdentityProvider('nonexistent.{{domains[]}}', {
    221      protocol: `non-existent`,
    222      usernameHint: `alice@example.org`,
    223    });
    224 
    225    return assert_rtcerror_rejection('idp-load-failure',
    226      pc.getIdentityAssertion());
    227  }, `getIdentityAssertion() should reject with RTCError('idp-load-failure') if IdP cannot be loaded`);
    228 
    229  /*
    230    9.3.1.  User Login Procedure
    231      Rejecting the promise returned by generateAssertion will cause the error to
    232      propagate to the application. Login errors are indicated by rejecting the
    233      promise with an RTCError with errorDetail set to "idp-need-login".
    234 
    235      The URL to login at will be passed to the application in the idpLoginUrl
    236      attribute of the RTCPeerConnection.
    237 
    238    9.5.  IdP Error Handling
    239      - If the identity provider requires the user to login, the operation will fail
    240        RTCError with errorDetail set to "idp-need-login" and the idpLoginUrl attribute
    241        of the error set to the URL that can be used to login.
    242   */
    243  promise_test(t => {
    244    const pc = new RTCPeerConnection();
    245 
    246    assert_equals(pc.idpLoginUrl, null,
    247      'Expect initial pc.idpLoginUrl to be null');
    248 
    249    const port = window.location.port;
    250    const [idpDomain] = getIdpDomains();
    251    const idpHost = hostString(idpDomain, port);
    252 
    253    pc.setIdentityProvider(idpHost, {
    254      protocol: `mock-idp.js?generatorAction=require-login`,
    255      usernameHint: `alice@${idpDomain}`,
    256    });
    257 
    258    return assert_rtcerror_rejection('idp-need-login',
    259      pc.getIdentityAssertion())
    260    .then(err => {
    261      assert_equals(err.idpLoginUrl, `https://${idpHost}/login`,
    262        'Expect err.idpLoginUrl to be set to url set by mock-idp.js');
    263 
    264      assert_equals(pc.idpLoginUrl, `https://${idpHost}/login`,
    265        'Expect pc.idpLoginUrl to be set to url set by mock-idp.js');
    266 
    267      assert_equals(pc.idpErrorInfo, 'login required',
    268        'Expect pc.idpErrorInfo to be set to info set by mock-idp.js');
    269    });
    270  }, `getIdentityAssertion() should reject with RTCError('idp-need-login') when mock-idp.js requires login`);
    271 
    272  /*
    273    RTCIdentityProviderOptions Members
    274      peerIdentity
    275        The identity of the peer. For identity providers that bind their assertions to a
    276        particular pair of communication peers, this allows them to generate an assertion
    277        that includes both local and remote identities. If this value is omitted, but a
    278        value is provided for the peerIdentity member of RTCConfiguration, the value from
    279        RTCConfiguration is used.
    280  */
    281  promise_test(t => {
    282    const pc = new RTCPeerConnection({
    283      peerIdentity: 'bob@example.net'
    284    });
    285 
    286    const port = window.location.port;
    287    const [idpDomain] = getIdpDomains();
    288    const idpHost = hostString(idpDomain, port);
    289 
    290    pc.setIdentityProvider(idpHost, {
    291      protocol: 'mock-idp.js'
    292    });
    293 
    294    return pc.getIdentityAssertion()
    295    .then(assertionResultStr => {
    296      const { assertion } = parseAssertionResult(assertionResultStr);
    297      assert_equals(assertion.args.options.peerIdentity, 'bob@example.net');
    298    });
    299  }, 'setIdentityProvider() with no peerIdentity provided should use peerIdentity value from getConfiguration()');
    300 
    301  /*
    302    9.6.  setIdentityProvider
    303      3.  If any identity provider value has changed, discard any stored identity assertion.
    304   */
    305  promise_test(t => {
    306    const pc = new RTCPeerConnection();
    307    const port = window.location.port;
    308    const [idpDomain] = getIdpDomains();
    309    const idpHost = hostString(idpDomain, port);
    310 
    311    pc.setIdentityProvider(idpHost, {
    312      protocol: 'mock-idp.js?mark=first'
    313    });
    314 
    315    return pc.getIdentityAssertion()
    316    .then(assertionResultStr => {
    317      const { assertion } = parseAssertionResult(assertionResultStr);
    318      assert_equals(assertion.query.mark, 'first');
    319 
    320      pc.setIdentityProvider(idpHost, {
    321        protocol: 'mock-idp.js?mark=second'
    322      });
    323 
    324      return pc.getIdentityAssertion();
    325    })
    326    .then(assertionResultStr => {
    327      const { assertion } = parseAssertionResult(assertionResultStr);
    328      assert_equals(assertion.query.mark, 'second',
    329        'Expect generated assertion is from second IdP config');
    330    });
    331  }, `Calling setIdentityProvider() multiple times should reset identity assertions`);
    332 
    333  promise_test(t => {
    334    const pc = new RTCPeerConnection();
    335    const port = window.location.port;
    336    const [idpDomain] = getIdpDomains();
    337 
    338    pc.setIdentityProvider(hostString(idpDomain, port), {
    339      protocol: 'mock-idp.js',
    340      usernameHint: `alice@${idpDomain}`
    341    });
    342 
    343    return pc.getIdentityAssertion()
    344    .then(assertionResultStr =>
    345      pc.createOffer()
    346      .then(offer => {
    347        assert_true(offer.sdp.includes(`\r\na=identity:${assertionResultStr}`,
    348          'Expect SDP to have a=identity line containing assertion string'));
    349      }));
    350  }, 'createOffer() should return SDP containing identity assertion string if identity provider is set');
    351 
    352  /*
    353    6. Requesting Identity Assertions
    354 
    355      The identity assertion request process is triggered by a call to
    356      createOffer, createAnswer, or getIdentityAssertion. When these calls are
    357      invoked and an identity provider has been set, the following steps are
    358      executed:
    359 
    360      ...
    361 
    362      If assertion generation fails, then the promise for the corresponding
    363      function call is rejected with a newly created OperationError.   */
    364  promise_test(t => {
    365    const pc = new RTCPeerConnection();
    366    const port = window.location.port;
    367    const [idpDomain] = getIdpDomains();
    368 
    369    pc.setIdentityProvider(hostString(idpDomain, port), {
    370      protocol: 'mock-idp.js?generatorAction=throw-error',
    371      usernameHint: `alice@${idpDomain}`
    372    });
    373 
    374    return promise_rejects_dom(t, 'OperationError',
    375      pc.createOffer());
    376  }, 'createOffer() should reject with OperationError if identity assertion request fails');
    377 
    378  promise_test(t => {
    379    const pc = new RTCPeerConnection();
    380    const port = window.location.port;
    381    const [idpDomain] = getIdpDomains();
    382 
    383    pc.setIdentityProvider(hostString(idpDomain, port), {
    384      protocol: 'mock-idp.js?generatorAction=throw-error',
    385      usernameHint: `alice@${idpDomain}`
    386    });
    387 
    388    return new RTCPeerConnection()
    389    .createOffer()
    390    .then(offer => pc.setRemoteDescription(offer))
    391    .then(() =>
    392      promise_rejects_dom(t, 'OperationError',
    393        pc.createAnswer()));
    394 
    395  }, 'createAnswer() should reject with OperationError if identity assertion request fails');
    396 
    397 </script>