| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCPeerConnection.prototype.getIdentityAssertion</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="identity-helper.sub.js"></script> |
| <script> |
| 'use strict'; |
| |
| // Test is based on the following editor draft: |
| // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html |
| |
| // The tests here interacts with the mock identity provider located at |
| // /.well-known/idp-proxy/mock-idp.js |
| |
| // The following helper functions are called from identity-helper.sub.js |
| // parseAssertionResult |
| // getIdpDomains |
| // assert_rtcerror_rejection |
| // hostString |
| |
| /* |
| 9.6. RTCPeerConnection Interface Extensions |
| partial interface RTCPeerConnection { |
| void setIdentityProvider(DOMString provider, |
| optional RTCIdentityProviderOptions options); |
| Promise<DOMString> getIdentityAssertion(); |
| readonly attribute Promise<RTCIdentityAssertion> peerIdentity; |
| readonly attribute DOMString? idpLoginUrl; |
| readonly attribute DOMString? idpErrorInfo; |
| }; |
| |
| dictionary RTCIdentityProviderOptions { |
| DOMString protocol = "default"; |
| DOMString usernameHint; |
| DOMString peerIdentity; |
| }; |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| |
| const [idpDomain] = getIdpDomains(); |
| const idpHost = hostString(idpDomain, port); |
| |
| pc.setIdentityProvider(idpHost, { |
| protocol: 'mock-idp.js?foo=bar', |
| usernameHint: `alice@${idpDomain}`, |
| peerIdentity: 'bob@example.org' |
| }); |
| |
| return pc.getIdentityAssertion() |
| .then(assertionResultStr => { |
| const { idp, assertion } = parseAssertionResult(assertionResultStr); |
| |
| assert_equals(idp.domain, idpHost, |
| 'Expect mock-idp.js to construct domain from its location.host'); |
| |
| assert_equals(idp.protocol, 'mock-idp.js', |
| 'Expect mock-idp.js to return protocol of itself with no query string'); |
| |
| const { |
| watermark, |
| args, |
| env, |
| query, |
| } = assertion; |
| |
| assert_equals(watermark, 'mock-idp.js.watermark', |
| 'Expect assertion result to contain watermark left by mock-idp.js'); |
| |
| assert_equals(args.origin, window.origin, |
| 'Expect args.origin argument to be the origin of this window'); |
| |
| assert_equals(env.location, |
| `https://${idpHost}/.well-known/idp-proxy/idp-test.js?foo=bar`, |
| 'Expect IdP proxy to be loaded with full well-known URL constructed from provider and protocol'); |
| |
| assert_equals(env.origin, `https://${idpHost}`, |
| 'Expect IdP to have its own origin'); |
| |
| assert_equals(args.options.protocol, 'idp-test.js?foo=bar', |
| 'Expect options.protocol to be the same value as being passed from here'); |
| |
| assert_equals(args.options.usernameHint, `alice@${idpDomain}`, |
| 'Expect options.usernameHint to be the same value as being passed from here'); |
| |
| assert_equals(args.options.peerIdentity, 'bob@example.org', |
| 'Expect options.peerIdentity to be the same value as being passed from here'); |
| |
| assert_equals(query.foo, 'bar', |
| 'Expect query string to be parsed by mock-idp.js and returned back'); |
| }); |
| }, 'getIdentityAssertion() should load IdP proxy and return assertion generated'); |
| |
| // When generating assertion, the RTCPeerConnection doesn't care if the returned assertion |
| // represents identity of different domain |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| |
| const [idpDomain1, idpDomain2] = getIdpDomains(); |
| assert_not_equals(idpDomain1, idpDomain2, |
| 'Sanity check two idpDomains are different'); |
| |
| // Ask mock-idp.js to return a custom domain idpDomain2 and custom protocol foo |
| pc.setIdentityProvider(hostString(idpDomain1, port), { |
| protocol: `mock-idp.js?generatorAction=return-custom-idp&domain=${idpDomain2}&protocol=foo`, |
| usernameHint: `alice@${idpDomain2}`, |
| }); |
| |
| return pc.getIdentityAssertion() |
| .then(assertionResultStr => { |
| const { idp, assertion } = parseAssertionResult(assertionResultStr); |
| assert_equals(idp.domain, idpDomain2); |
| assert_equals(idp.protocol, 'foo'); |
| assert_equals(assertion.options.usernameHint, `alice@${idpDomain2}`); |
| }); |
| }, 'getIdentityAssertion() should succeed if mock-idp.js return different domain and protocol in assertion'); |
| |
| /* |
| 9.3. Requesting Identity Assertions |
| 4. If the IdP proxy produces an error or returns a promise that does not resolve to |
| a valid RTCIdentityValidationResult (see 9.5 IdP Error Handling), then identity |
| validation fails. |
| |
| 9.5. IdP Error Handling |
| - If an identity provider throws an exception or returns a promise that is ultimately |
| rejected, then the procedure that depends on the IdP MUST also fail. These types of |
| errors will cause an IdP failure with an RTCError with errorDetail set to |
| "idp-execution-failure". |
| |
| 9.6. RTCPeerConnection Interface Extensions |
| idpErrorInfo |
| An attribute that the IdP can use to pass additional information back to the |
| applications about the error. The format of this string is defined by the IdP and |
| may be JSON. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| |
| assert_equals(pc.idpErrorInfo, null, |
| 'Expect initial pc.idpErrorInfo to be null'); |
| |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| // Ask mock-idp.js to throw an error with err.errorInfo set to bar |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: `mock-idp.js?generatorAction=throw-error&errorInfo=bar`, |
| usernameHint: `alice@${idpDomain}`, |
| }); |
| |
| return assert_rtcerror_rejection('idp-execution-failure', |
| pc.getIdentityAssertion()) |
| .then(() => { |
| assert_equals(pc.idpErrorInfo, 'bar', |
| 'Expect pc.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js'); |
| }); |
| }, `getIdentityAssertion() should reject with RTCError('idp-execution-failure') if mock-idp.js throws error`); |
| |
| /* |
| 9.5. IdP Error Handling |
| - If the script loaded from the identity provider is not valid JavaScript or does |
| not implement the correct interfaces, it causes an IdP failure with an RTCError |
| with errorDetail set to "idp-bad-script-failure". |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| // Ask mock-idp.js to not register its callback to the |
| // RTCIdentityProviderRegistrar |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: `mock-idp.js?action=do-not-register`, |
| usernameHint: `alice@${idpDomain}`, |
| }); |
| |
| return assert_rtcerror_rejection('idp-bad-script-failure', |
| pc.getIdentityAssertion()); |
| |
| }, `getIdentityAssertion() should reject with RTCError('idp-bad-script-failure') if IdP proxy script do not register its callback`); |
| |
| /* |
| 9.3. Requesting Identity Assertions |
| 4. If the IdP proxy produces an error or returns a promise that does not resolve |
| to a valid RTCIdentityAssertionResult (see 9.5 IdP Error Handling), then assertion |
| generation fails. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| // Ask mock-idp.js to return an invalid result that is not proper |
| // RTCIdentityAssertionResult |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: `mock-idp.js?generatorAction=return-invalid-result`, |
| usernameHint: `alice@${idpDomain}`, |
| }); |
| |
| return promise_rejects(t, 'OperationError', |
| pc.getIdentityAssertion()); |
| }, `getIdentityAssertion() should reject with OperationError if mock-idp.js return invalid result`); |
| |
| /* |
| 9.5. IdP Error Handling |
| - A RTCPeerConnection might be configured with an identity provider, but loading of |
| the IdP URI fails. Any procedure that attempts to invoke such an identity provider |
| and cannot load the URI fails with an RTCError with errorDetail set to |
| "idp-load-failure" and the httpRequestStatusCode attribute of the error set to the |
| HTTP status code of the response. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| |
| pc.setIdentityProvider('nonexistent.{{domains[]}}', { |
| protocol: `non-existent`, |
| usernameHint: `alice@example.org`, |
| }); |
| |
| return assert_rtcerror_rejection('idp-load-failure', |
| pc.getIdentityAssertion()); |
| }, `getIdentityAssertion() should reject with RTCError('idp-load-failure') if IdP cannot be loaded`); |
| |
| /* |
| 9.3.1. User Login Procedure |
| Rejecting the promise returned by generateAssertion will cause the error to |
| propagate to the application. Login errors are indicated by rejecting the |
| promise with an RTCError with errorDetail set to "idp-need-login". |
| |
| The URL to login at will be passed to the application in the idpLoginUrl |
| attribute of the RTCPeerConnection. |
| |
| 9.5. IdP Error Handling |
| - If the identity provider requires the user to login, the operation will fail |
| RTCError with errorDetail set to "idp-need-login" and the idpLoginUrl attribute |
| of the error set to the URL that can be used to login. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| |
| assert_equals(pc.idpLoginUrl, null, |
| 'Expect initial pc.idpLoginUrl to be null'); |
| |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| const idpHost = hostString(idpDomain, port); |
| |
| pc.setIdentityProvider(idpHost, { |
| protocol: `mock-idp.js?generatorAction=require-login`, |
| usernameHint: `alice@${idpDomain}`, |
| }); |
| |
| return assert_rtcerror_rejection('idp-need-login', |
| pc.getIdentityAssertion()) |
| .then(err => { |
| assert_equals(err.idpLoginUrl, `https://${idpHost}/login`, |
| 'Expect err.idpLoginUrl to be set to url set by mock-idp.js'); |
| |
| assert_equals(pc.idpLoginUrl, `https://${idpHost}/login`, |
| 'Expect pc.idpLoginUrl to be set to url set by mock-idp.js'); |
| |
| assert_equals(pc.idpErrorInfo, 'login required', |
| 'Expect pc.idpErrorInfo to be set to info set by mock-idp.js'); |
| }); |
| }, `getIdentityAssertion() should reject with RTCError('idp-need-login') when mock-idp.js requires login`); |
| |
| /* |
| RTCIdentityProviderOptions Members |
| peerIdentity |
| The identity of the peer. For identity providers that bind their assertions to a |
| particular pair of communication peers, this allows them to generate an assertion |
| that includes both local and remote identities. If this value is omitted, but a |
| value is provided for the peerIdentity member of RTCConfiguration, the value from |
| RTCConfiguration is used. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection({ |
| peerIdentity: 'bob@example.net' |
| }); |
| |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| const idpHost = hostString(idpDomain, port); |
| |
| pc.setIdentityProvider(idpHost, { |
| protocol: 'mock-idp.js' |
| }); |
| |
| return pc.getIdentityAssertion() |
| .then(assertionResultStr => { |
| const { assertion } = parseAssertionResult(assertionResultStr); |
| assert_equals(assertion.args.options.peerIdentity, 'bob@example.net'); |
| }); |
| }, 'setIdentityProvider() with no peerIdentity provided should use peerIdentity value from getConfiguration()'); |
| |
| /* |
| 9.6. setIdentityProvider |
| 3. If any identity provider value has changed, discard any stored identity assertion. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| const idpHost = hostString(idpDomain, port); |
| |
| pc.setIdentityProvider(idpHost, { |
| protocol: 'mock-idp.js?mark=first' |
| }); |
| |
| return pc.getIdentityAssertion() |
| .then(assertionResultStr => { |
| const { assertion } = parseAssertionResult(assertionResultStr); |
| assert_equals(assertion.query.mark, 'first'); |
| |
| pc.setIdentityProvider(idpHost, { |
| protocol: 'mock-idp.js?mark=second' |
| }); |
| |
| return pc.getIdentityAssertion(); |
| }) |
| .then(assertionResultStr => { |
| const { assertion } = parseAssertionResult(assertionResultStr); |
| assert_equals(assertion.query.mark, 'second', |
| 'Expect generated assertion is from second IdP config'); |
| }); |
| }, `Calling setIdentityProvider() multiple times should reset identity assertions`); |
| |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: 'mock-idp.js', |
| usernameHint: `alice@${idpDomain}` |
| }); |
| |
| return pc.getIdentityAssertion() |
| .then(assertionResultStr => |
| pc.createOffer() |
| .then(offer => { |
| assert_true(offer.sdp.includes(`\r\na=identity:${assertionResultStr}`, |
| 'Expect SDP to have a=identity line containing assertion string')); |
| })); |
| }, 'createOffer() should return SDP containing identity assertion string if identity provider is set'); |
| |
| /* |
| 4.4.2. Steps to create an offer |
| 1. If the need for an identity assertion was identified when createOffer was |
| invoked, wait for the identity assertion request process to complete. |
| 2. If the identity provider was unable to produce an identity assertion, reject p |
| with a newly created NotReadableError and abort these steps. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: 'mock-idp.js?generatorAction=throw-error', |
| usernameHint: `alice@${idpDomain}` |
| }); |
| |
| return promise_rejects(t, 'NotReadableError', |
| pc.createOffer()); |
| }, 'createOffer() should reject with NotReadableError if identitity assertion request fails'); |
| |
| /* |
| 4.4.2. Steps to create an answer |
| 1. If the need for an identity assertion was identified when createAnswer was |
| invoked, wait for the identity assertion request process to complete. |
| |
| 2. If the identity provider was unable to produce an identity assertion, reject p |
| with a newly created NotReadableError and abort these steps. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| const port = window.location.port; |
| const [idpDomain] = getIdpDomains(); |
| |
| pc.setIdentityProvider(hostString(idpDomain, port), { |
| protocol: 'mock-idp.js?generatorAction=throw-error', |
| usernameHint: `alice@${idpDomain}` |
| }); |
| |
| return new RTCPeerConnection() |
| .createOffer() |
| .then(offer => pc.setRemoteDescription(offer)) |
| .then(() => |
| promise_rejects(t, 'NotReadableError', |
| pc.createAnswer())); |
| |
| }, 'createAnswer() should reject with NotReadableError if identitity assertion request fails'); |
| |
| </script> |