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