| <!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.href, | |
| `https://${idpHost}/.well-known/idp-proxy/mock-idp.js?foo=bar`, | |
| 'Expect IdP proxy to be loaded with full well-known URL constructed from provider and protocol'); | |
| assert_equals(env.location.origin, `https://${idpHost}`, | |
| 'Expect IdP to have its own origin'); | |
| assert_equals(args.options.protocol, 'mock-idp.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.args.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_dom(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'); | |
| /* | |
| 6. Requesting Identity Assertions | |
| The identity assertion request process is triggered by a call to | |
| createOffer, createAnswer, or getIdentityAssertion. When these calls are | |
| invoked and an identity provider has been set, the following steps are | |
| executed: | |
| ... | |
| If assertion generation fails, then the promise for the corresponding | |
| function call is rejected with a newly created OperationError. */ | |
| 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_dom(t, 'OperationError', | |
| pc.createOffer()); | |
| }, 'createOffer() should reject with OperationError if identity assertion request fails'); | |
| 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_dom(t, 'OperationError', | |
| pc.createAnswer())); | |
| }, 'createAnswer() should reject with OperationError if identity assertion request fails'); | |
| </script> |