OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview JavaScript implementation of the credential management API |
| 7 * defined at http://w3c.github.io/webappsec/specs/credentialmanagement. |
| 8 * This is a minimal implementation that sends data to the app side to |
| 9 * integrate with the password manager. When loaded, installs the API onto |
| 10 * the window.navigator object. |
| 11 */ |
| 12 |
| 13 // Namespace for all credential management stuff. __gCrWeb must have already |
| 14 // been defined. |
| 15 __gCrWeb['credentialManager'] = { |
| 16 /** |
| 17 * The next ID for forwarding a call from JS to the App and tracking its |
| 18 * associated Promise resolvers and rejecters. |
| 19 * @private {number} |
| 20 */ |
| 21 nextId_: 0, |
| 22 |
| 23 /** |
| 24 * Tracks navigator.credentials Promise resolvers. |
| 25 * @type {!Object<number, function(?Credential|undefined)>} |
| 26 * @private |
| 27 */ |
| 28 resolvers_: {}, |
| 29 |
| 30 /** |
| 31 * Tracks navigator.credentials Promise rejecters. |
| 32 * @type {!Object<number, function(?Error)>} |
| 33 * @private |
| 34 */ |
| 35 rejecters_: {} |
| 36 }; |
| 37 |
| 38 |
| 39 /** @enum {string} */ |
| 40 __gCrWeb.credentialManager.CredentialType = { |
| 41 CREDENTIAL: 'Credential', |
| 42 PASSWORD_CREDENTIAL: 'PasswordCredential', |
| 43 FEDERATED_CREDENTIAL: 'FederatedCredential', |
| 44 PENDING_CREDENTIAL: 'PendingCredential' |
| 45 }; |
| 46 |
| 47 |
| 48 /** @typedef { |
| 49 * type: __gCrWeb.credentialManager.CredentialType, |
| 50 * id: string, |
| 51 * name: (string|undefined), |
| 52 * avatarURL: (string|undefined), |
| 53 * federation: (string|undefined) |
| 54 * } |
| 55 */ |
| 56 __gCrWeb.credentialManager.SerializedCredential; |
| 57 |
| 58 |
| 59 /** |
| 60 * Creates and returns a Promise whose resolver and rejecter functions are |
| 61 * stored with the associated |requestId|. They can be accessed by calling |
| 62 * |resolve| or |reject| on __gCrWeb['credentialManager'] with that ID. |
| 63 * @param {number} requestId An identifier to track the resolver and rejecter |
| 64 * associated with the returned Promise. |
| 65 * @return {!Promise<?Credential|undefined>} A new Promise. |
| 66 * @private |
| 67 */ |
| 68 __gCrWeb['credentialManager'].createPromise_ = function(requestId) { |
| 69 return new Promise(function(resolve, reject) { |
| 70 __gCrWeb['credentialManager'].resolvers_[requestId] = resolve; |
| 71 __gCrWeb['credentialManager'].rejecters_[requestId] = reject; |
| 72 }); |
| 73 }; |
| 74 |
| 75 |
| 76 /** |
| 77 * Deletes the resolver and rejecter of the Promise associated with |requestId|. |
| 78 * @param {number} requestId The identifier of the Promise. |
| 79 * @private |
| 80 */ |
| 81 __gCrWeb['credentialManager'].removePromise_ = function(requestId) { |
| 82 delete __gCrWeb['credentialManager'].rejecters_[requestId]; |
| 83 delete __gCrWeb['credentialManager'].resolvers_[requestId]; |
| 84 }; |
| 85 |
| 86 |
| 87 /** |
| 88 * Parses |credentialData| into a Credential object. |
| 89 * @param {!__gCrWeb.credentialManager.SerializedCredential} credentialData A |
| 90 * simple object representation of a Credential like might be obtained from |
| 91 * Credential.prototype.serialize. |
| 92 * @return {?Credential} A Credential object, or null if parsing was |
| 93 * unsuccessful. |
| 94 * @private |
| 95 */ |
| 96 __gCrWeb['credentialManager'].parseCredential_ = function(credentialData) { |
| 97 var CredentialType = __gCrWeb.credentialManager.CredentialType; |
| 98 switch (credentialData['type']) { |
| 99 case CredentialType.CREDENTIAL: |
| 100 return Credential.parse(credentialData); |
| 101 case CredentialType.PASSWORD_CREDENTIAL: |
| 102 return PasswordCredential.parse(credentialData); |
| 103 case CredentialType.FEDERATED_CREDENTIAL: |
| 104 return FederatedCredential.parse(credentialData); |
| 105 case CredentialType.PENDING_CREDENTIAL: |
| 106 return PendingCredential.parse(credentialData); |
| 107 default: |
| 108 return null; |
| 109 } |
| 110 }; |
| 111 |
| 112 |
| 113 /** |
| 114 * Resolves the Promise that was created with the given |requestId| with a |
| 115 * Credential object parsed from |opt_credentialData| and removes that promise |
| 116 * from the global state. Future attempts to resolve or reject the Promise will |
| 117 * fail. |
| 118 * @param {number} requestId The identifier of the Promise to resolve. |
| 119 * @param {Object=} opt_credentialData An object describing a credential. If |
| 120 * provided, this parameter will be parsed into the appropriate Credential |
| 121 * type. |
| 122 * @return {boolean} Indicates whether the Promise was successfully resolved. |
| 123 */ |
| 124 __gCrWeb['credentialManager'].resolve = function(requestId, |
| 125 opt_credentialData) { |
| 126 var resolver = __gCrWeb['credentialManager'].resolvers_[requestId]; |
| 127 if (!resolver) { |
| 128 return false; |
| 129 } |
| 130 if (opt_credentialData) { |
| 131 var credential = null; |
| 132 try { |
| 133 credential = |
| 134 __gCrWeb['credentialManager'].parseCredential_(opt_credentialData); |
| 135 } catch (e) { |
| 136 // Failed to parse |opt_credentialData|. The app side sent bad data. |
| 137 return false; |
| 138 } |
| 139 resolver(credential); |
| 140 } else { |
| 141 resolver(); |
| 142 } |
| 143 __gCrWeb['credentialManager'].removePromise_(requestId); |
| 144 return true; |
| 145 }; |
| 146 |
| 147 |
| 148 /** |
| 149 * Rejects the Promise that was created with the given |requestId| and cleans up |
| 150 * that Promise. |
| 151 * @param {number} requestId The identifier of the Promise to resolve. |
| 152 * @param {string} errorType The type of the Error to pass to the rejecter |
| 153 * function. If not recognized, a standard JavaScript Error will be used. |
| 154 * @param {string} message A human-readable description of the error. |
| 155 * @return {boolean} Indicates whether the Promise was successfully rejected. |
| 156 */ |
| 157 __gCrWeb['credentialManager'].reject = function(requestId, errorType, message) { |
| 158 var rejecter = __gCrWeb['credentialManager'].rejecters_[requestId]; |
| 159 if (!rejecter) { |
| 160 return false; |
| 161 } |
| 162 var error = null; |
| 163 if (errorType == 'SecurityError') { |
| 164 error = new SecurityError(message); |
| 165 } else if (errorType == 'InvalidStateError') { |
| 166 error = new InvalidStateError(message); |
| 167 } else { |
| 168 error = new Error(message); |
| 169 } |
| 170 rejecter(error); |
| 171 __gCrWeb['credentialManager'].removePromise_(requestId); |
| 172 return true; |
| 173 }; |
| 174 |
| 175 |
| 176 /** |
| 177 * Sends a command representing |method| of navigator.credentials to the app |
| 178 * side with the given |opt_options|. |
| 179 * @param {string} command The name of the command being sent. |
| 180 * @param {Object=} opt_options A dictionary of additional properties to forward |
| 181 * to the app. |
| 182 * @return {!Promise<?Credential|undefined>} A promise for the result. |
| 183 * @private |
| 184 */ |
| 185 __gCrWeb['credentialManager'].invokeOnHost_ = function(command, opt_options) { |
| 186 var requestId = __gCrWeb['credentialManager'].nextId_++; |
| 187 var message = { |
| 188 'command': command, |
| 189 'requestId': requestId |
| 190 }; |
| 191 if (opt_options) { |
| 192 Object.keys(opt_options).forEach(function(key) { |
| 193 message[key] = opt_options[key]; |
| 194 }); |
| 195 } |
| 196 __gCrWeb.message.invokeOnHost(message); |
| 197 return __gCrWeb['credentialManager'].createPromise_(requestId); |
| 198 }; |
| 199 |
| 200 |
| 201 |
| 202 /** |
| 203 * Creates a new SecurityError to represent failures caused by violating |
| 204 * security requirements of the API. |
| 205 * @param {string} message A human-readable message describing this error. |
| 206 * @extends {Error} |
| 207 * @constructor |
| 208 */ |
| 209 function SecurityError(message) { |
| 210 Error.call(this, message); |
| 211 this.name = 'SecurityError'; |
| 212 this.message = message; |
| 213 } |
| 214 SecurityError.prototype = Object.create(Error.prototype); |
| 215 SecurityError.prototype.constructor = SecurityError; |
| 216 |
| 217 |
| 218 |
| 219 /** |
| 220 * Creates a new InvalidStateError to represent failures caused by inconsistent |
| 221 * internal state. |
| 222 * @param {string} message A human-readable message describing this error. |
| 223 * @extends {Error} |
| 224 * @constructor |
| 225 */ |
| 226 function InvalidStateError(message) { |
| 227 Error.call(this, message); |
| 228 this.name = 'InvalidStateError'; |
| 229 this.message = message; |
| 230 } |
| 231 InvalidStateError.prototype = Object.create(Error.prototype); |
| 232 InvalidStateError.prototype.constructor = InvalidStateError; |
| 233 |
| 234 |
| 235 |
| 236 /** |
| 237 * Creates a new Credential object. For more information, see |
| 238 * https://w3c.github.io/webappsec/specs/credentialmanagement/#credential |
| 239 * @param {string} id The credential’s identifier. This might be a username or |
| 240 * email address, for instance. |
| 241 * @param {string=} opt_name A name associated with the credential, intended as |
| 242 * a human-understandable public name. |
| 243 * @param {string=} opt_avatarUrl A URL pointing to an avatar image for the |
| 244 * user. This URL MUST NOT be an a priori insecure URL. |
| 245 * @constructor |
| 246 */ |
| 247 function Credential(id, opt_name, opt_avatarUrl) { |
| 248 if (id === null || id === undefined) |
| 249 throw new TypeError('id must be provided'); |
| 250 /** |
| 251 * The credential's identifier. Read-only. |
| 252 * @type {string} |
| 253 */ |
| 254 this.id; |
| 255 Object.defineProperty(this, 'id', { |
| 256 configurable: false, |
| 257 enumerable: true, |
| 258 value: id, |
| 259 writable: false |
| 260 }); |
| 261 /** |
| 262 * A human-understandable public name associated with the credential. |
| 263 * Read-only. |
| 264 * @type {string} |
| 265 */ |
| 266 this.name; |
| 267 Object.defineProperty(this, 'name', { |
| 268 configurable: false, |
| 269 enumerable: true, |
| 270 value: opt_name || '', |
| 271 writable: false |
| 272 }); |
| 273 /** |
| 274 * A URL pointing to an avatar image for the user. Read-only. |
| 275 * NOTE: This property name deviates from the Google JavaScript style guide |
| 276 * in order to meet the public API specification. |
| 277 * @type {string} |
| 278 */ |
| 279 this.avatarURL; |
| 280 Object.defineProperty(this, 'avatarURL', { |
| 281 configurable: false, |
| 282 enumerable: true, |
| 283 value: opt_avatarUrl || '', |
| 284 writable: false |
| 285 }); |
| 286 } |
| 287 |
| 288 |
| 289 /** |
| 290 * Returns a simple object representation of this credential suitable for |
| 291 * sending to the app side. |
| 292 * @return {__gCrWeb.credentialManager.SerializedCredential} An object |
| 293 * representing this credential. |
| 294 */ |
| 295 Credential.prototype.serialize = function() { |
| 296 var serialized = { |
| 297 'type': this.constructor.name, |
| 298 'id': this.id, |
| 299 'name': this.name |
| 300 }; |
| 301 if (this.avatarURL && this.avatarURL.length > 0) |
| 302 serialized['avatarURL'] = this.avatarURL; |
| 303 return serialized; |
| 304 }; |
| 305 |
| 306 |
| 307 /** |
| 308 * Parses |credentialData| into a Credential object. |
| 309 * @param {!__gCrWeb.credentialManager.SerializedCredential} credentialData A |
| 310 * simple object representation of a Credential like might be obtained from |
| 311 * Credential.prototype.serialize. |
| 312 * @return {Credential} A Credential object. |
| 313 */ |
| 314 Credential.parse = function(credentialData) { |
| 315 return new Credential(credentialData['id'], |
| 316 credentialData['name'], |
| 317 credentialData['avatarURL']); |
| 318 }; |
| 319 |
| 320 |
| 321 |
| 322 /** |
| 323 * Creates a new PasswordCredential object. For more information, see |
| 324 * https://w3c.github.io/webappsec/specs/credentialmanagement/#localcredential |
| 325 * @param {string} id The credential’s identifier. This might be a username or |
| 326 * email address, for instance. |
| 327 * @param {string} password The credential's password. |
| 328 * @param {string=} opt_name A name associated with the credential, intended as |
| 329 * a human-understandable public name. |
| 330 * @param {string=} opt_avatarUrl A URL pointing to an avatar image for the |
| 331 * user. This URL MUST NOT be an a priori insecure URL. |
| 332 * @constructor |
| 333 * @extends {Credential} |
| 334 */ |
| 335 function PasswordCredential(id, password, opt_name, opt_avatarUrl) { |
| 336 if (password === null || password === undefined) |
| 337 throw new TypeError('password must be provided'); |
| 338 Credential.call(this, id, opt_name, opt_avatarUrl); |
| 339 var formData = new FormData(); |
| 340 formData.append('username', id); |
| 341 formData.append('password', password); |
| 342 /** |
| 343 * A FormData object, containing two entries: one named username, the other |
| 344 * named password. Read-only. |
| 345 * @type {FormData} |
| 346 */ |
| 347 this.formData; |
| 348 Object.defineProperty(this, 'formData', { |
| 349 configurable: false, |
| 350 enumerable: true, |
| 351 value: formData, |
| 352 writable: false |
| 353 }); |
| 354 /** |
| 355 * The credential's password. |
| 356 * @type {string} |
| 357 * @private |
| 358 */ |
| 359 this.password_; |
| 360 Object.defineProperty(this, 'password_', { |
| 361 configurable: false, |
| 362 enumerable: false, |
| 363 value: password, |
| 364 writable: false |
| 365 }); |
| 366 } |
| 367 PasswordCredential.prototype = Object.create(Credential.prototype); |
| 368 PasswordCredential.prototype.constructor = PasswordCredential; |
| 369 |
| 370 |
| 371 /** |
| 372 * Returns a simple object representation of this credential suitable for |
| 373 * sending to the app side. |
| 374 * @return {__gCrWeb.credentialManager.SerializedCredential} An object |
| 375 * representing this credential. |
| 376 */ |
| 377 PasswordCredential.prototype.serialize = function() { |
| 378 var obj = Credential.prototype.serialize.call(this); |
| 379 obj.password = this.password_; |
| 380 return obj; |
| 381 }; |
| 382 |
| 383 |
| 384 /** |
| 385 * Parses |credentialData| into a PasswordCredential object. |
| 386 * @param {!__gCrWeb.credentialManager.SerializedCredential} credentialData A |
| 387 * simple object representation of a PasswordCredential like might be |
| 388 * obtained from PasswordCredential.prototype.serialize. |
| 389 * @return {PasswordCredential} A PasswordCredential object. |
| 390 */ |
| 391 PasswordCredential.parse = function(credentialData) { |
| 392 return new PasswordCredential(credentialData['id'], |
| 393 credentialData['password'], |
| 394 credentialData['name'], |
| 395 credentialData['avatarURL']); |
| 396 }; |
| 397 |
| 398 |
| 399 |
| 400 /** |
| 401 * Creates a new FederatedCredential object. For more information, see |
| 402 * https://w3c.github.io/webappsec/specs/credentialmanagement/#federatedcredenti
al |
| 403 * @param {string} id The credential’s identifier. This might be a username or |
| 404 * email address, for instance. |
| 405 * @param {string} federation The credential’s federation. For details regarding |
| 406 * valid formats, see https://w3c.github.io/webappsec/specs/credentialmanage
ment/#identifying-federations |
| 407 * @param {string=} opt_name A name associated with the credential, intended as |
| 408 * a human-understandable public name. |
| 409 * @param {string=} opt_avatarUrl A URL pointing to an avatar image for the |
| 410 * user. This URL MUST NOT be an a priori insecure URL. |
| 411 * @constructor |
| 412 * @extends {Credential} |
| 413 */ |
| 414 function FederatedCredential(id, federation, opt_name, opt_avatarUrl) { |
| 415 if (federation === null || federation === undefined) |
| 416 throw new TypeError('federation must be provided'); |
| 417 Credential.call(this, id, opt_name, opt_avatarUrl); |
| 418 /** |
| 419 * The credential’s federation. Read-only. |
| 420 * @type {string} |
| 421 */ |
| 422 this.federation; |
| 423 Object.defineProperty(this, 'federation', { |
| 424 configurable: false, |
| 425 enumerable: true, |
| 426 value: federation, |
| 427 writable: false |
| 428 }); |
| 429 } |
| 430 FederatedCredential.prototype = Object.create(Credential.prototype); |
| 431 FederatedCredential.prototype.constructor = FederatedCredential; |
| 432 |
| 433 |
| 434 /** |
| 435 * Returns a simple object representation of this credential suitable for |
| 436 * sending to the app side. |
| 437 * @return {__gCrWeb.credentialManager.SerializedCredential} An object |
| 438 * representing this credential. |
| 439 */ |
| 440 FederatedCredential.prototype.serialize = function() { |
| 441 var obj = Credential.prototype.serialize.call(this); |
| 442 obj.federation = this.federation; |
| 443 return obj; |
| 444 }; |
| 445 |
| 446 |
| 447 /** |
| 448 * Parses |credentialData| into a FederatedCredential object. |
| 449 * @param {!__gCrWeb.credentialManager.SerializedCredential} credentialData A |
| 450 * simple object representation of a FederatedCredential like might be |
| 451 * obtained from FederatedCredential.prototype.serialize. |
| 452 * @return {!FederatedCredential} A FederatedCredential object. |
| 453 */ |
| 454 FederatedCredential.parse = function(credentialData) { |
| 455 return new FederatedCredential(credentialData['id'], |
| 456 credentialData['federation'], |
| 457 credentialData['name'], |
| 458 credentialData['avatarURL']); |
| 459 }; |
| 460 |
| 461 |
| 462 |
| 463 /** |
| 464 * Creates a new PendingCredential object. For more information, see |
| 465 * https://w3c.github.io/webappsec/specs/credentialmanagement/#pendingcredential |
| 466 * @param {string} id The credential’s identifier. This might be a username or |
| 467 * email address, for instance. |
| 468 * @param {string=} opt_name A name associated with the credential, intended as |
| 469 * a human-understandable public name. |
| 470 * @param {string=} opt_avatarUrl A URL pointing to an avatar image for the |
| 471 * user. This URL MUST NOT be an a priori insecure URL. |
| 472 * @constructor |
| 473 * @extends {Credential} |
| 474 */ |
| 475 function PendingCredential(id, opt_name, opt_avatarUrl) { |
| 476 Credential.call(this, id, opt_name, opt_avatarUrl); |
| 477 } |
| 478 PendingCredential.prototype = Object.create(Credential.prototype); |
| 479 PendingCredential.prototype.constructor = PendingCredential; |
| 480 |
| 481 |
| 482 /** |
| 483 * Parses |credentialData| into a PendingCredential object. |
| 484 * @param {!__gCrWeb.credentialManager.SerializedCredential} credentialData A |
| 485 * simple object representation of a PendingCredential like might be |
| 486 * obtained from PendingCredential.prototype.serialize. |
| 487 * @return {!Credential} A PendingCredential object. |
| 488 */ |
| 489 PendingCredential.parse = function(credentialData) { |
| 490 return new PendingCredential(credentialData['id'], |
| 491 credentialData['name'], |
| 492 credentialData['avatarURL']); |
| 493 }; |
| 494 |
| 495 |
| 496 |
| 497 /** |
| 498 * Implements the public Credential Management API. For more information, see |
| 499 * http://w3c.github.io/webappsec/specs/credentialmanagement/#interfaces-credent
ial-manager |
| 500 * @constructor |
| 501 */ |
| 502 function CredentialsContainer() { |
| 503 } |
| 504 |
| 505 |
| 506 /** |
| 507 * Requests a credential from the credential manager. |
| 508 * @param {{suppressUI: boolean, federations: Array<string>}=} opt_options An |
| 509 * optional dictionary of parameters for the request. If |suppressUI| is |
| 510 * true, the returned promise will only be resolved with a credential if |
| 511 * this is possible without user interaction; otherwise, the returned |
| 512 * promise will be resolved with |undefined|. |federations| specifies a |
| 513 * list of acceptable federation providers. For more information, see |
| 514 * https://w3c.github.io/webappsec/specs/credentialmanagement/#interfaces-re
quest-options |
| 515 * @return {!Promise<?Credential|undefined>} A promise for retrieving the result |
| 516 * of the request. |
| 517 */ |
| 518 CredentialsContainer.prototype.request = function(opt_options) { |
| 519 var options = { |
| 520 'suppressUI': !!opt_options && !!opt_options['suppressUI'], |
| 521 'federations': (!!opt_options && opt_options['federations']) || [] |
| 522 }; |
| 523 return __gCrWeb['credentialManager'].invokeOnHost_( |
| 524 'navigator.credentials.request', options); |
| 525 }; |
| 526 |
| 527 |
| 528 /** |
| 529 * Notifies the browser that the user has successfully signed in. |
| 530 * @param {Credential=} opt_successfulCredential The credential that was used |
| 531 * to sign in. |
| 532 * @return {!Promise<?Credential|undefined>} A promise to wait for |
| 533 * acknowledgement from the browser. |
| 534 */ |
| 535 CredentialsContainer.prototype.notifySignedIn = function( |
| 536 opt_successfulCredential) { |
| 537 var options = opt_successfulCredential && { |
| 538 'credential': opt_successfulCredential.serialize() |
| 539 }; |
| 540 return __gCrWeb['credentialManager'].invokeOnHost_( |
| 541 'navigator.credentials.notifySignedIn', options); |
| 542 }; |
| 543 |
| 544 |
| 545 /** |
| 546 * Notifies the browser that the user failed to sign in. |
| 547 * @param {Credential=} opt_failedCredential The credential that failed to |
| 548 * sign in. |
| 549 * @return {!Promise<?Credential|undefined>} A promise to wait for |
| 550 * acknowledgement from the browser. |
| 551 */ |
| 552 CredentialsContainer.prototype.notifyFailedSignIn = function( |
| 553 opt_failedCredential) { |
| 554 var options = opt_failedCredential && { |
| 555 'credential': opt_failedCredential.serialize() |
| 556 }; |
| 557 return __gCrWeb['credentialManager'].invokeOnHost_( |
| 558 'navigator.credentials.notifyFailedSignIn', options); |
| 559 }; |
| 560 |
| 561 |
| 562 /** |
| 563 * Notifies the browser that the user signed out. |
| 564 * @return {!Promise<?Credential|undefined>} A promise to wait for |
| 565 * acknowledgement from the browser. |
| 566 */ |
| 567 CredentialsContainer.prototype.notifySignedOut = function() { |
| 568 return __gCrWeb['credentialManager'].invokeOnHost_( |
| 569 'navigator.credentials.notifySignedOut'); |
| 570 }; |
| 571 |
| 572 |
| 573 // Install the public interface. |
| 574 window.navigator.credentials = new CredentialsContainer(); |
OLD | NEW |