OLD | NEW |
1 <link rel="import" href="../polymer/polymer.html"> | 1 <link rel="import" href="../polymer/polymer.html"> |
2 <link rel="import" href="../google-apis/google-js-api.html"> | 2 <link rel="import" href="../google-apis/google-js-api.html"> |
3 | 3 |
4 <script> | 4 <script> |
5 (function() { | 5 (function() { |
6 | 6 |
7 /** | 7 /** |
8 * Enum of attributes to be passed through to the login API call. | 8 * Enum of attributes to be passed through to the login API call. |
9 * @readonly | 9 * @readonly |
10 * @enum {string} | 10 * @enum {string} |
11 */ | 11 */ |
12 var ProxyLoginAttributes = { | 12 var ProxyLoginAttributes = { |
13 'appPackageName': 'apppackagename', | 13 'appPackageName': 'apppackagename', |
14 'clientId': 'clientid', | 14 'clientId': 'clientid', |
15 'cookiePolicy': 'cookiepolicy', | 15 'cookiePolicy': 'cookiepolicy', |
16 'requestVisibleActions': 'requestvisibleactions' | 16 'requestVisibleActions': 'requestvisibleactions', |
| 17 'hostedDomain': 'hostedDomain' |
17 }; | 18 }; |
18 | 19 |
19 /** | 20 /** |
20 * AuthEngine does all interactions with gapi.auth2 | 21 * AuthEngine does all interactions with gapi.auth2 |
21 * | 22 * |
22 * It is tightly coupled with <google-signin-aware> element | 23 * It is tightly coupled with <google-signin-aware> element |
23 * The elements configure AuthEngine. | 24 * The elements configure AuthEngine. |
24 * AuthEngine propagates all authentication events to all google-signin-awar
e elements | 25 * AuthEngine propagates all authentication events to all google-signin-awar
e elements |
25 * | 26 * |
26 * API used: https://developers.google.com/identity/sign-in/web/reference | 27 * API used: https://developers.google.com/identity/sign-in/web/reference |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
90 }, | 91 }, |
91 | 92 |
92 set requestVisibleactions(val) { | 93 set requestVisibleactions(val) { |
93 if (this._requestVisibleActions && val && val != this._requestVisibleAct
ions) { | 94 if (this._requestVisibleActions && val && val != this._requestVisibleAct
ions) { |
94 throw new Error('requestVisibleactions cannot change. Values do not ma
tch. New: ' + val + ' Old: ' + this._requestVisibleActions); | 95 throw new Error('requestVisibleactions cannot change. Values do not ma
tch. New: ' + val + ' Old: ' + this._requestVisibleActions); |
95 } | 96 } |
96 if (val) | 97 if (val) |
97 this._requestVisibleActions = val; | 98 this._requestVisibleActions = val; |
98 }, | 99 }, |
99 | 100 |
| 101 /** |
| 102 * oauth2 argument, set by google-signin-aware |
| 103 */ |
| 104 _hostedDomain: '', |
| 105 |
| 106 get hostedDomain() { |
| 107 return this._hostedDomain; |
| 108 }, |
| 109 |
| 110 set hostedDomain(val) { |
| 111 if (this._hostedDomain && val && val != this._hostedDomain) { |
| 112 throw new Error('hostedDomain cannot change. Values do not match. New:
' + val + ' Old: ' + this._hostedDomain); |
| 113 } |
| 114 if (val) |
| 115 this._hostedDomain = val; |
| 116 }, |
| 117 |
| 118 /** Is offline access currently enabled in the google-signin-aware element
? */ |
| 119 _offline: false, |
| 120 |
| 121 get offline() { |
| 122 return this._offline; |
| 123 }, |
| 124 |
| 125 set offline(val) { |
| 126 this._offline = val; |
| 127 this.updateAdditionalAuth(); |
| 128 }, |
| 129 |
| 130 /** Should we force a re-prompt for offline access? */ |
| 131 _offlineAlwaysPrompt: false, |
| 132 |
| 133 get offlineAlwaysPrompt() { |
| 134 return this._offlineAlwaysPrompt; |
| 135 }, |
| 136 |
| 137 set offlineAlwaysPrompt(val) { |
| 138 this._offlineAlwaysPrompt = val; |
| 139 this.updateAdditionalAuth(); |
| 140 }, |
| 141 |
| 142 /** Have we already gotten offline access from Google during this session?
*/ |
| 143 offlineGranted: false, |
| 144 |
100 /** <google-js-api> */ | 145 /** <google-js-api> */ |
101 _apiLoader: null, | 146 _apiLoader: null, |
102 | 147 |
103 /** an array of wanted scopes. oauth2 argument */ | 148 /** an array of wanted scopes. oauth2 argument */ |
104 _requestedScopeArray: [], | 149 _requestedScopeArray: [], |
105 | 150 |
106 /** _requestedScopeArray as string */ | 151 /** _requestedScopeArray as string */ |
107 get requestedScopes() { | 152 get requestedScopes() { |
108 return this._requestedScopeArray.join(' '); | 153 return this._requestedScopeArray.join(' '); |
109 }, | 154 }, |
(...skipping 25 matching lines...) Expand all Loading... |
135 gapi.load('auth2', this.initAuth2.bind(this)); | 180 gapi.load('auth2', this.initAuth2.bind(this)); |
136 }, | 181 }, |
137 | 182 |
138 initAuth2: function() { | 183 initAuth2: function() { |
139 if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId)
{ | 184 if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId)
{ |
140 return; | 185 return; |
141 } | 186 } |
142 var auth = gapi.auth2.init({ | 187 var auth = gapi.auth2.init({ |
143 'client_id': this.clientId, | 188 'client_id': this.clientId, |
144 'cookie_policy': this.cookiePolicy, | 189 'cookie_policy': this.cookiePolicy, |
145 'scope': this.requestedScopes | 190 'scope': this.requestedScopes, |
| 191 'hosted_domain': this.hostedDomain |
146 }); | 192 }); |
147 | 193 |
148 auth.currentUser.listen(this.handleUserUpdate.bind(this)); | 194 auth.currentUser.listen(this.handleUserUpdate.bind(this)); |
149 | 195 |
150 auth.then( | 196 auth.then( |
151 function success() { | 197 function success() { |
152 // Let the current user listener trigger the changes. | 198 // Let the current user listener trigger the changes. |
153 }, | 199 }, |
154 function error(error) { | 200 function error(error) { |
155 console.error(error); | 201 console.error(error); |
(...skipping 16 matching lines...) Expand all Loading... |
172 newPrimaryUser.getGrantedScopes()); | 218 newPrimaryUser.getGrantedScopes()); |
173 // console.log(this._grantedScopeArray); | 219 // console.log(this._grantedScopeArray); |
174 this.updateAdditionalAuth(); | 220 this.updateAdditionalAuth(); |
175 | 221 |
176 var response = newPrimaryUser.getAuthResponse(); | 222 var response = newPrimaryUser.getAuthResponse(); |
177 for (var i=0; i<this.signinAwares.length; i++) { | 223 for (var i=0; i<this.signinAwares.length; i++) { |
178 this.signinAwares[i]._updateScopeStatus(response); | 224 this.signinAwares[i]._updateScopeStatus(response); |
179 } | 225 } |
180 }, | 226 }, |
181 | 227 |
| 228 setOfflineCode: function(code) { |
| 229 for (var i=0; i<this.signinAwares.length; i++) { |
| 230 this.signinAwares[i]._updateOfflineCode(code); |
| 231 } |
| 232 }, |
| 233 |
182 /** convert scope string to scope array */ | 234 /** convert scope string to scope array */ |
183 strToScopeArray: function(str) { | 235 strToScopeArray: function(str) { |
184 if (!str) { | 236 if (!str) { |
185 return []; | 237 return []; |
186 } | 238 } |
187 // remove extra spaces, then split | 239 // remove extra spaces, then split |
188 var scopes = str.replace(/\ +/g, ' ').trim().split(' '); | 240 var scopes = str.replace(/\ +/g, ' ').trim().split(' '); |
189 for (var i=0; i<scopes.length; i++) { | 241 for (var i=0; i<scopes.length; i++) { |
190 scopes[i] = scopes[i].toLowerCase(); | 242 scopes[i] = scopes[i].toLowerCase(); |
191 // Handle scopes that will be deprecated but are still returned with
their old value | 243 // Handle scopes that will be deprecated but are still returned with
their old value |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
230 } | 282 } |
231 if (scopesUpdated) { | 283 if (scopesUpdated) { |
232 this.updateAdditionalAuth(); | 284 this.updateAdditionalAuth(); |
233 this.updatePlusScopes(); | 285 this.updatePlusScopes(); |
234 } | 286 } |
235 }, | 287 }, |
236 | 288 |
237 /** update status of _needAdditionalAuth */ | 289 /** update status of _needAdditionalAuth */ |
238 updateAdditionalAuth: function() { | 290 updateAdditionalAuth: function() { |
239 var needMoreAuth = false; | 291 var needMoreAuth = false; |
240 for (var i=0; i<this._requestedScopeArray.length; i++) { | 292 if ((this.offlineAlwaysPrompt || this.offline ) && !this.offlineGranted)
{ |
241 if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) ===
-1) { | 293 needMoreAuth = true; |
242 needMoreAuth = true; | 294 } else { |
243 break; | 295 for (var i=0; i<this._requestedScopeArray.length; i++) { |
| 296 if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) ==
= -1) { |
| 297 needMoreAuth = true; |
| 298 break; |
| 299 } |
244 } | 300 } |
245 } | 301 } |
246 if (this._needAdditionalAuth != needMoreAuth) { | 302 if (this._needAdditionalAuth != needMoreAuth) { |
247 this._needAdditionalAuth = needMoreAuth; | 303 this._needAdditionalAuth = needMoreAuth; |
248 // broadcast new value | 304 // broadcast new value |
249 for (var i=0; i<this.signinAwares.length; i++) { | 305 for (var i=0; i<this.signinAwares.length; i++) { |
250 this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth); | 306 this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth); |
251 } | 307 } |
252 } | 308 } |
253 }, | 309 }, |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
320 | 376 |
321 // Proxy specific attributes through to the signIn options. | 377 // Proxy specific attributes through to the signIn options. |
322 Object.keys(ProxyLoginAttributes).forEach(function(key) { | 378 Object.keys(ProxyLoginAttributes).forEach(function(key) { |
323 if (this[key] && this[key] !== '') { | 379 if (this[key] && this[key] !== '') { |
324 params[ProxyLoginAttributes[key]] = this[key]; | 380 params[ProxyLoginAttributes[key]] = this[key]; |
325 } | 381 } |
326 }, this); | 382 }, this); |
327 | 383 |
328 var promise; | 384 var promise; |
329 var user = gapi.auth2.getAuthInstance().currentUser.get(); | 385 var user = gapi.auth2.getAuthInstance().currentUser.get(); |
330 if (user.getGrantedScopes()) { | 386 if (!(this.offline || this.offlineAlwaysPrompt)) { |
331 // additional auth, skip multiple account dialog | 387 if (user.getGrantedScopes()) { |
332 promise = user.grant(params); | 388 // additional auth, skip multiple account dialog |
| 389 promise = user.grant(params); |
| 390 } else { |
| 391 // initial signin |
| 392 promise = gapi.auth2.getAuthInstance().signIn(params); |
| 393 } |
333 } else { | 394 } else { |
334 // initial signin | 395 params.redirect_uri = 'postmessage'; |
335 promise = gapi.auth2.getAuthInstance().signIn(params); | 396 if (this.offlineAlwaysPrompt) { |
| 397 params.approval_prompt = 'force'; |
| 398 } |
| 399 |
| 400 // Despite being documented at https://goo.gl/tiO0Bk |
| 401 // It doesn't seem like user.grantOfflineAccess() actually exists in |
| 402 // the current version of the Google Sign-In JS client we're using |
| 403 // through GoogleWebComponents. So in the offline case, we will not |
| 404 // distinguish between a first auth and an additional one. |
| 405 promise = gapi.auth2.getAuthInstance().grantOfflineAccess(params); |
336 } | 406 } |
337 promise.then( | 407 promise.then( |
338 function success(newUser) { | 408 function success(response) { |
| 409 // If login was offline, response contains one string "code" |
| 410 // Otherwise it contains the user object already |
| 411 var newUser; |
| 412 if (response.code) { |
| 413 AuthEngine.offlineGranted = true; |
| 414 newUser = gapi.auth2.getAuthInstance().currentUser.get(); |
| 415 AuthEngine.setOfflineCode(response.code); |
| 416 } else { |
| 417 newUser = response; |
| 418 } |
| 419 |
339 var authResponse = newUser.getAuthResponse(); | 420 var authResponse = newUser.getAuthResponse(); |
340 // Let the current user listener trigger the changes. | 421 // Let the current user listener trigger the changes. |
341 }, | 422 }, |
342 function error(error) { | 423 function error(error) { |
343 if ("Access denied." == error.reason) { | 424 if ("Access denied." == error.reason) { |
344 // Access denied is not an error, user hit cancel | 425 // Access denied is not an error, user hit cancel |
345 return; | 426 return; |
346 } else { | 427 } else { |
347 console.error(error); | 428 console.error(error); |
348 } | 429 } |
(...skipping 19 matching lines...) Expand all Loading... |
368 | 449 |
369 /** | 450 /** |
370 `google-signin-aware` is used to enable authentication in custom elements by | 451 `google-signin-aware` is used to enable authentication in custom elements by |
371 interacting with a google-signin element that needs to be present somewhere | 452 interacting with a google-signin element that needs to be present somewhere |
372 on the page. | 453 on the page. |
373 | 454 |
374 The `scopes` attribute allows you to specify which scope permissions are require
d | 455 The `scopes` attribute allows you to specify which scope permissions are require
d |
375 (e.g do you want to allow interaction with the Google Drive API). | 456 (e.g do you want to allow interaction with the Google Drive API). |
376 | 457 |
377 The `google-signin-aware-success` event is triggered when a user successfully | 458 The `google-signin-aware-success` event is triggered when a user successfully |
378 authenticates. The `google-signin-aware-signed-out` event is triggered | 459 authenticates. If either `offline` or `offlineAlwaysPrompt` is set to true, succ
essful |
379 when a user explicitely signs out via the google-signin element. | 460 authentication will also trigger the `google-signin-offline-success`event. |
| 461 The `google-signin-aware-signed-out` event is triggered when a user explicitly |
| 462 signs out via the google-signin element. |
380 | 463 |
381 You can bind to `isAuthorized` property to monitor authorization state. | 464 You can bind to `isAuthorized` property to monitor authorization state. |
382 ##### Example | 465 ##### Example |
383 | 466 |
384 <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google
-signin-aware> | 467 <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google
-signin-aware> |
| 468 |
| 469 |
| 470 ##### Example with offline |
| 471 <template id="awareness" is="dom-bind"> |
| 472 <google-signin-aware |
| 473 scopes="https://www.googleapis.com/auth/drive" |
| 474 offline |
| 475 on-google-signin-aware-success="handleSignin" |
| 476 on-google-signin-offline-success="handleOffline"></google-signin-aware
> |
| 477 <\/template> |
| 478 <script> |
| 479 var aware = document.querySelector('#awareness'); |
| 480 aware.handleSignin = function(response) { |
| 481 var user = gapi.auth2.getAuthInstance().currentUser.get(); |
| 482 console.log('User name: ' + user.getBasicProfile().getName()); |
| 483 }; |
| 484 aware.handleOffline = function(response) { |
| 485 console.log('Offline code received: ' + response.detail.code); |
| 486 // Here you would POST response.detail.code to your webserver, which can |
| 487 // exchange the authorization code for an access token. More info at: |
| 488 // https://developers.google.com/identity/protocols/OAuth2WebServer |
| 489 }; |
| 490 <\/script> |
385 */ | 491 */ |
386 Polymer({ | 492 Polymer({ |
387 | 493 |
388 is: 'google-signin-aware', | 494 is: 'google-signin-aware', |
389 | 495 |
390 /** | 496 /** |
391 * Fired when this scope has been authorized | 497 * Fired when this scope has been authorized |
392 * @param {Object} result Authorization result. | 498 * @param {Object} result Authorization result. |
393 * @event google-signin-aware-success | 499 * @event google-signin-aware-success |
394 */ | 500 */ |
395 /** | 501 /** |
| 502 * Fired when an offline authorization is successful. |
| 503 * @param {Object} detail |
| 504 * @param {string} detail.code The one-time authorization code from Google
. |
| 505 * Your application can exchange this for an `access_token` and `refre
sh_token` |
| 506 * @event google-signin-offline-success |
| 507 */ |
| 508 /** |
396 * Fired when this scope is not authorized | 509 * Fired when this scope is not authorized |
397 * @event google-signin-aware-signed-out | 510 * @event google-signin-aware-signed-out |
398 */ | 511 */ |
399 properties: { | 512 properties: { |
400 /** | 513 /** |
401 * App package name for android over-the-air installs. | 514 * App package name for android over-the-air installs. |
402 * See the relevant [docs](https://developers.google.com/+/web/signin/an
droid-app-installs) | 515 * See the relevant [docs](https://developers.google.com/+/web/signin/an
droid-app-installs) |
403 */ | 516 */ |
404 appPackageName: { | 517 appPackageName: { |
405 type: String, | 518 type: String, |
(...skipping 21 matching lines...) Expand all Loading... |
427 /** | 540 /** |
428 * The app activity types you want to write on behalf of the user | 541 * The app activity types you want to write on behalf of the user |
429 * (e.g http://schemas.google.com/AddActivity) | 542 * (e.g http://schemas.google.com/AddActivity) |
430 * | 543 * |
431 */ | 544 */ |
432 requestVisibleActions: { | 545 requestVisibleActions: { |
433 type: String, | 546 type: String, |
434 observer: '_requestVisibleActionsChanged' | 547 observer: '_requestVisibleActionsChanged' |
435 }, | 548 }, |
436 | 549 |
| 550 /** |
| 551 * The Google Apps domain to which users must belong to sign in. |
| 552 * See the relevant [docs](https://developers.google.com/identity/sign-i
n/web/reference) for more information. |
| 553 */ |
| 554 hostedDomain: { |
| 555 type: String, |
| 556 observer: '_hostedDomainChanged' |
| 557 }, |
| 558 |
| 559 /** |
| 560 * Allows for offline `access_token` retrieval during the signin process
. |
| 561 * See also `offlineAlwaysPrompt`. You only need to set one of the two;
if both |
| 562 * are set, the behavior of `offlineAlwaysPrompt` will override `offline
`. |
| 563 */ |
| 564 offline: { |
| 565 type: Boolean, |
| 566 value: false, |
| 567 observer: '_offlineChanged' |
| 568 }, |
| 569 |
| 570 /** |
| 571 * Works the same as `offline` with the addition that it will always |
| 572 * force a re-prompt to the user, guaranteeing that you will get a |
| 573 * refresh_token even if the user has already granted offline access to |
| 574 * this application. You only need to set one of `offline` or |
| 575 * `offlineAlwaysPrompt`, not both. |
| 576 */ |
| 577 offlineAlwaysPrompt: { |
| 578 type: Boolean, |
| 579 value: false, |
| 580 observer: '_offlineAlwaysPromptChanged' |
| 581 }, |
| 582 |
437 /** | 583 /** |
438 * The scopes to provide access to (e.g https://www.googleapis.com/auth/
drive) | 584 * The scopes to provide access to (e.g https://www.googleapis.com/auth/
drive) |
439 * and should be space-delimited. | 585 * and should be space-delimited. |
440 */ | 586 */ |
441 scopes: { | 587 scopes: { |
442 type: String, | 588 type: String, |
443 value: 'profile', | 589 value: 'profile', |
444 observer: '_scopesChanged' | 590 observer: '_scopesChanged' |
445 }, | 591 }, |
446 | 592 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
510 }, | 656 }, |
511 | 657 |
512 _cookiePolicyChanged: function(newPolicy, oldPolicy) { | 658 _cookiePolicyChanged: function(newPolicy, oldPolicy) { |
513 AuthEngine.cookiePolicy = newPolicy; | 659 AuthEngine.cookiePolicy = newPolicy; |
514 }, | 660 }, |
515 | 661 |
516 _requestVisibleActionsChanged: function(newVal, oldVal) { | 662 _requestVisibleActionsChanged: function(newVal, oldVal) { |
517 AuthEngine.requestVisibleActions = newVal; | 663 AuthEngine.requestVisibleActions = newVal; |
518 }, | 664 }, |
519 | 665 |
| 666 _hostedDomainChanged: function(newVal, oldVal) { |
| 667 AuthEngine.hostedDomain = newVal; |
| 668 }, |
| 669 |
| 670 _offlineChanged: function(newVal, oldVal) { |
| 671 AuthEngine.offline = newVal; |
| 672 }, |
| 673 |
| 674 _offlineAlwaysPromptChanged: function(newVal, oldVal) { |
| 675 AuthEngine.offlineAlwaysPrompt = newVal; |
| 676 }, |
| 677 |
520 _scopesChanged: function(newVal, oldVal) { | 678 _scopesChanged: function(newVal, oldVal) { |
521 AuthEngine.requestScopes(newVal); | 679 AuthEngine.requestScopes(newVal); |
522 this._updateScopeStatus(); | 680 this._updateScopeStatus(); |
523 }, | 681 }, |
524 | 682 |
525 _updateScopeStatus: function(user) { | 683 _updateScopeStatus: function(user) { |
526 var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.sc
opes); | 684 var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.sc
opes); |
527 if (newAuthorized !== this.isAuthorized) { | 685 if (newAuthorized !== this.isAuthorized) { |
528 this._setIsAuthorized(newAuthorized); | 686 this._setIsAuthorized(newAuthorized); |
529 if (newAuthorized) { | 687 if (newAuthorized) { |
530 this.fire('google-signin-aware-success', user); | 688 this.fire('google-signin-aware-success', user); |
531 } | 689 } |
532 else { | 690 else { |
533 this.fire('google-signin-aware-signed-out', user); | 691 this.fire('google-signin-aware-signed-out', user); |
534 } | 692 } |
535 } | 693 } |
| 694 }, |
| 695 |
| 696 _updateOfflineCode: function(code) { |
| 697 if (code) { |
| 698 this.fire('google-signin-offline-success', {code: code}); |
| 699 } |
536 } | 700 } |
537 }); | 701 }); |
538 })(); | 702 })(); |
539 </script> | 703 </script> |
OLD | NEW |