summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-identity
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc-identity')
-rw-r--r--testing/web-platform/tests/webrtc-identity/META.yml4
-rw-r--r--testing/web-platform/tests/webrtc-identity/RTCPeerConnection-constructor.html11
-rw-r--r--testing/web-platform/tests/webrtc-identity/RTCPeerConnection-getIdentityAssertion.sub.https.html397
-rw-r--r--testing/web-platform/tests/webrtc-identity/RTCPeerConnection-peerIdentity.https.html328
-rw-r--r--testing/web-platform/tests/webrtc-identity/identity-helper.sub.js70
-rw-r--r--testing/web-platform/tests/webrtc-identity/idlharness.https.window.js24
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) {}
+ }
+);