diff options
Diffstat (limited to 'testing/web-platform/tests/webrtc-identity')
6 files changed, 834 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc-identity/META.yml b/testing/web-platform/tests/webrtc-identity/META.yml new file mode 100644 index 0000000000..fb919db954 --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/META.yml @@ -0,0 +1,4 @@ +spec: https://w3c.github.io/webrtc-identity/identity.html +suggested_reviewers: + - martinthomson + - jan-ivar diff --git a/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-constructor.html b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-constructor.html new file mode 100644 index 0000000000..e7b7016338 --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-constructor.html @@ -0,0 +1,11 @@ +<!doctype html> +<meta charset=utf-8> +<title>RTCPeerConnection constructor</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +test(() => { + const toStringThrows = { toString: function() { throw new Error; } }; + assert_throws_js(Error, () => new RTCPeerConnection({ peerIdentity: toStringThrows })); +}, "RTCPeerConnection constructor throws if the given peerIdentity getter throws"); +</script> diff --git a/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-getIdentityAssertion.sub.https.html b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-getIdentityAssertion.sub.https.html new file mode 100644 index 0000000000..57d7b16165 --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-getIdentityAssertion.sub.https.html @@ -0,0 +1,397 @@ +<!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> diff --git a/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-peerIdentity.https.html b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-peerIdentity.https.html new file mode 100644 index 0000000000..268e406211 --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-peerIdentity.https.html @@ -0,0 +1,328 @@ +<!doctype html> +<meta charset=utf-8> +<title>RTCPeerConnection.prototype.peerIdentity</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-identity/identity.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; + }; + + [Constructor(DOMString idp, DOMString name)] + interface RTCIdentityAssertion { + attribute DOMString idp; + attribute DOMString name; + }; + */ + + /* + 4.3.2. setRemoteDescription + If an a=identity attribute is present in the session description, the browser + validates the identity assertion.. + + If the "peerIdentity" configuration is applied to the RTCPeerConnection, this + establishes a target peer identity of the provided value. Alternatively, if the + RTCPeerConnection has previously authenticated the identity of the peer (that + is, there is a current value for peerIdentity ), then this also establishes a + target peer identity. + */ + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + + t.add_cleanup(() => pc2.close()); + + const port = window.location.port; + const [idpDomain] = getIdpDomains(); + const idpHost = hostString(idpDomain, port); + + pc1.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js', + usernameHint: `alice@${idpDomain}` + }); + + const peerIdentity = pc2.peerIdentity; + await pc2.setRemoteDescription(await pc1.createOffer()); + const { idp, name } = await peerIdentity; + assert_equals(idp, idpHost, `Expect IdP to be ${idpHost}`); + assert_equals(name, `alice@${idpDomain}`, + `Expect validated identity from mock-idp.js to be same as specified in usernameHint`); + }, 'setRemoteDescription() on offer with a=identity should establish peerIdentity'); + + promise_test(async t => { + const port = window.location.port; + const [idpDomain] = getIdpDomains(); + const idpHost = hostString(idpDomain, port); + + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + pc1.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js', + usernameHint: `doesnt_matter@${idpDomain}` + }); + + const pc2 = new RTCPeerConnection({ + peerIdentity: `bob@${idpDomain}` + }); + + t.add_cleanup(() => pc2.close()); + + pc2.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js', + usernameHint: `alice@${idpDomain}` + }); + + const offer = await pc1.createOffer(); + + await promise_rejects_dom(t, 'OperationError', + pc2.setRemoteDescription(offer)); + await promise_rejects_dom(t, 'OperationError', + pc2.peerIdentity); + }, 'setRemoteDescription() on offer with a=identity that resolve to value different from target peer identity should reject with OperationError'); + + /* + 9.4. Verifying Identity Assertions + 8. The RTCPeerConnection decodes the contents and validates that it contains a + fingerprint value for every a=fingerprint attribute in the session description. + This ensures that the certificate used by the remote peer for communications + is covered by the identity assertion. + + If identity validation fails, the peerIdentity promise is rejected with a newly + created OperationError. + + If identity validation fails and there is a target peer identity for the + RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected + with the same DOMException. + */ + promise_test(t => { + const port = window.location.port; + const [idpDomain] = getIdpDomains(); + const idpHost = hostString(idpDomain, port); + + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection({ + peerIdentity: `alice@${idpDomain}` + }); + + t.add_cleanup(() => pc2.close()); + + // Ask mockidp.js to return custom contents in validation result + pc1.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus', + usernameHint: `alice@${idpDomain}` + }); + + const peerIdentityPromise = pc2.peerIdentity; + + return pc1.createOffer() + .then(offer => Promise.all([ + promise_rejects_dom(t, 'IdpError', + pc2.setRemoteDescription(offer)), + promise_rejects_dom(t, 'OperationError', + peerIdentityPromise) + ])); + }, 'setRemoteDescription() with peerIdentity set and with IdP proxy that return validationAssertion with mismatch contents should reject with OperationError'); + + /* + 9.4. Verifying Identity Assertions + 9. The RTCPeerConnection validates that the domain portion of the identity matches + the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check + fails then the identity validation fails. + */ + promise_test(t => { + const port = window.location.port; + const [idpDomain1, idpDomain2] = getIdpDomains(); + assert_not_equals(idpDomain1, idpDomain2, + 'Sanity check two idpDomains are different'); + + const idpHost1 = hostString(idpDomain1, port); + + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection({ + peerIdentity: `alice@${idpDomain2}` + }); + + t.add_cleanup(() => pc2.close()); + + // mock-idp.js will return assertion of domain2 identity + // with domain1 in the idp.domain field + pc1.setIdentityProvider(idpHost1, { + protocol: 'mock-idp.js', + usernameHint: `alice@${idpDomain2}` + }); + + return pc1.getIdentityAssertion() + .then(assertionResultStr => { + const { idp, assertion } = parseAssertionResult(assertionResultStr); + + assert_equals(idp.domain, idpHost1, + 'Sanity check domain of assertion is host1'); + + assert_equals(assertion.args.options.usernameHint, `alice@${idpDomain2}`, + 'Sanity check domain1 is going to validate a domain2 identity'); + + return pc1.createOffer(); + }) + .then(offer => Promise.all([ + promise_rejects_dom(t, 'OperationError', + pc2.setRemoteDescription(offer)), + promise_rejects_dom(t, 'OperationError', + pc2.peerIdentity) + ])); + }, 'setRemoteDescription() and peerIdentity should reject with OperationError if IdP return validated identity that is different from its own domain'); + + /* + 9.4 Verifying Identity Assertions + If identity validation fails and there is a target peer identity for the + RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected + with the same DOMException. + + 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". + + Any error generated by the IdP MAY provide additional information in the + idpErrorInfo attribute. The information in this string is defined by the + IdP in use. + */ + promise_test(t => { + const port = window.location.port; + const [idpDomain] = getIdpDomains(); + const idpHost = hostString(idpDomain, port); + + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection({ + peerIdentity: `alice@${idpDomain}` + }); + + t.add_cleanup(() => pc2.close()); + + // Ask mock-idp.js to throw error during validation, + // i.e. during pc2.setRemoteDescription() + pc1.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js?validatorAction=throw-error&errorInfo=bar', + usernameHint: `alice@${idpDomain}` + }); + + return pc1.createOffer() + .then(offer => Promise.all([ + assert_rtcerror_rejection('idp-execution-failure', + pc2.setRemoteDescription(offer)), + assert_rtcerror_rejection('idp-execution-failure', + pc2.peerIdentity) + ])) + .then(() => { + assert_equals(pc2.idpErrorInfo, 'bar', + 'Expect pc2.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js'); + }); + }, `When IdP throws error and pc has target peer identity, setRemoteDescription() and peerIdentity rejected with RTCError('idp-execution-error')`); + + /* + 4.3.2. setRemoteDescription + If there is no target peer identity, then setRemoteDescription does not await the + completion of identity validation. + + 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.4. Verifying Identity Assertions + If identity validation fails and there is no a target peer identity, the value of + the peerIdentity MUST be set to a new, unresolved promise instance. This permits + the use of renegotiation (or a subsequent answer, if the session description was + a provisional answer) to resolve or reject the identity. + */ + promise_test(t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + + t.add_cleanup(() => pc2.close()); + + const port = window.location.port; + const [idpDomain] = getIdpDomains(); + const idpHost = hostString(idpDomain, port); + + // Ask mock-idp.js to throw error during validation, + // i.e. during pc2.setRemoteDescription() + pc1.setIdentityProvider(idpHost, { + protocol: 'mock-idp.js?validatorAction=throw-error', + usernameHint: `alice@${idpDomain}` + }); + + const peerIdentityPromise1 = pc2.peerIdentity; + + return pc1.createOffer() + .then(offer => + // setRemoteDescription should succeed because there is no target peer identity set + pc2.setRemoteDescription(offer)) + .then(() => + assert_rtcerror_rejection('idp-execution-failure', + peerIdentityPromise1, + `Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`)) + .then(() => { + const peerIdentityPromise2 = pc2.peerIdentity; + assert_not_equals(peerIdentityPromise2, peerIdentityPromise1, + 'Expect pc2.peerIdentity to be replaced with a fresh unresolved promise'); + + // regenerate an identity assertion with no test option to throw error + pc1.setIdentityProvider(idpHost, { + protocol: 'idp-test.js', + usernameHint: `alice@${idpDomain}` + }); + + return pc1.createOffer() + .then(offer => pc2.setRemoteDescription(offer)) + .then(peerIdentityPromise2) + .then(identityAssertion => { + const { idp, name } = identityAssertion; + + assert_equals(idp, idpDomain, + `Expect IdP domain to be ${idpDomain}`); + + assert_equals(name, `alice@${idpDomain}`, + `Expect validated identity to be alice@${idpDomain}`); + + assert_equals(pc2.peeridentity, peerIdentityPromise2, + 'Expect pc2.peerIdentity to stay fixed after identity is validated'); + }); + }); + }, 'IdP failure with no target peer identity should have following setRemoteDescription() succeed and replace pc.peerIdentity with a new promise'); + +</script> diff --git a/testing/web-platform/tests/webrtc-identity/identity-helper.sub.js b/testing/web-platform/tests/webrtc-identity/identity-helper.sub.js new file mode 100644 index 0000000000..90363662f7 --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/identity-helper.sub.js @@ -0,0 +1,70 @@ +'use strict'; + +/* + In web-platform-test, a number of domains are required to be set up locally. + The list is available at docs/_writing-tests/server-features.md. The + appropriate hosts file entries can be generated with the WPT CLI via the + following command: `wpt make-hosts-file`. + */ + +/* + dictionary RTCIdentityProviderDetails { + required DOMString domain; + DOMString protocol = "default"; + }; + */ + +// Parse a base64 JSON encoded string returned from getIdentityAssertion(). +// This is also the string that is set in the a=identity line. +// Returns a { idp, assertion } where idp is of type RTCIdentityProviderDetails +// and assertion is the deserialized JSON that was returned by the +// IdP proxy's generateAssertion() function. +function parseAssertionResult(assertionResultStr) { + const assertionResult = JSON.parse(atob(assertionResultStr)); + + const { idp } = assertionResult; + const assertion = JSON.parse(assertionResult.assertion); + + return { idp, assertion }; +} + +// Return two distinct IdP domains that are different from current domain +function getIdpDomains() { + const domainA = '{{domains[www]}}'; + const domainB = '{{domains[www1]}}'; + const domainC = '{{domains[www2]}}'; + + if(window.location.hostname === domainA) { + return [domainB, domainC]; + } else if(window.location.hostname === domainB) { + return [domainA, domainC]; + } else { + return [domainA, domainB]; + } +} + +function assert_rtcerror_rejection(errorDetail, promise, desc) { + return promise.then( + res => { + assert_unreached(`Expect promise to be rejected with RTCError, but instead got ${res}`); + }, err => { + assert_true(err instanceof RTCError, + 'Expect error object to be instance of RTCError'); + + assert_equals(err.errorDetail, errorDetail, + `Expect RTCError object have errorDetail set to ${errorDetail}`); + + return err; + }); +} + +// construct a host string consist of domain and optionally port +// If the default HTTP/HTTPS port is used, window.location.port returns +// empty string. +function hostString(domain, port) { + if(port === '') { + return domain; + } else { + return `${domain}:${port}`; + } +} diff --git a/testing/web-platform/tests/webrtc-identity/idlharness.https.window.js b/testing/web-platform/tests/webrtc-identity/idlharness.https.window.js new file mode 100644 index 0000000000..8eb60c960a --- /dev/null +++ b/testing/web-platform/tests/webrtc-identity/idlharness.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +'use strict'; + +idl_test( + ['webrtc-identity'], + ['webrtc', 'mediacapture-streams', 'html', 'dom', 'webidl'], + async idlArray => { + idlArray.add_objects({ + RTCPeerConnection: [`new RTCPeerConnection()`], + RTCIdentityAssertion: [`new RTCIdentityAssertion('idp', 'name')`], + MediaStreamTrack: ['track'], + // TODO: RTCIdentityProviderGlobalScope + // TODO: RTCIdentityProviderRegistrar + }); + + try { + self.track = await navigator.mediaDevices + .getUserMedia({audio: true}) + .then(m => m.getTracks()[0]); + } catch (e) {} + } +); |