OLD | NEW |
(Empty) | |
| 1 <link rel="import" href="../polymer/polymer.html"> |
| 2 <link rel="import" href="../google-apis/google-js-api.html"> |
| 3 |
| 4 <script> |
| 5 (function() { |
| 6 |
| 7 /** |
| 8 * Enum of attributes to be passed through to the login API call. |
| 9 * @readonly |
| 10 * @enum {string} |
| 11 */ |
| 12 var ProxyLoginAttributes = { |
| 13 'appPackageName': 'apppackagename', |
| 14 'clientId': 'clientid', |
| 15 'cookiePolicy': 'cookiepolicy', |
| 16 'requestVisibleActions': 'requestvisibleactions' |
| 17 }; |
| 18 |
| 19 /** |
| 20 * AuthEngine does all interactions with gapi.auth2 |
| 21 * |
| 22 * It is tightly coupled with <google-signin-aware> element |
| 23 * The elements configure AuthEngine. |
| 24 * AuthEngine propagates all authentication events to all google-signin-awar
e elements |
| 25 * |
| 26 * API used: https://developers.google.com/identity/sign-in/web/reference |
| 27 * |
| 28 */ |
| 29 var AuthEngine = { |
| 30 |
| 31 /** |
| 32 * oauth2 argument, set by google-signin-aware |
| 33 */ |
| 34 _clientId: null, |
| 35 |
| 36 get clientId() { |
| 37 return this._clientId; |
| 38 }, |
| 39 |
| 40 set clientId(val) { |
| 41 if (this._clientId && val && val != this._clientId) { |
| 42 throw new Error('clientId cannot change. Values do not match. New: ' +
val + ' Old:' + this._clientId); |
| 43 } |
| 44 if (val) { |
| 45 this._clientId = val; |
| 46 this.initAuth2(); |
| 47 } |
| 48 }, |
| 49 |
| 50 /** |
| 51 * oauth2 argument, set by google-signin-aware |
| 52 */ |
| 53 _cookiePolicy: 'single_host_origin', |
| 54 |
| 55 get cookiePolicy() { |
| 56 return this._cookiePolicy; |
| 57 }, |
| 58 |
| 59 set cookiePolicy(val) { |
| 60 if (val) { |
| 61 this._cookiePolicy = val; |
| 62 } |
| 63 }, |
| 64 |
| 65 /** |
| 66 * oauth2 argument, set by google-signin-aware |
| 67 */ |
| 68 _appPackageName: '', |
| 69 |
| 70 get appPackageName() { |
| 71 return this._appPackageName; |
| 72 }, |
| 73 |
| 74 set appPackageName(val) { |
| 75 if (this._appPackageName && val && val != this._appPackageName) { |
| 76 throw new Error('appPackageName cannot change. Values do not match. Ne
w: ' + val + ' Old: ' + this._appPackageName); |
| 77 } |
| 78 if (val) { |
| 79 this._appPackageName = val; |
| 80 } |
| 81 }, |
| 82 |
| 83 /** |
| 84 * oauth2 argument, set by google-signin-aware |
| 85 */ |
| 86 _requestVisibleActions: '', |
| 87 |
| 88 get requestVisibleactions() { |
| 89 return this._requestVisibleActions; |
| 90 }, |
| 91 |
| 92 set requestVisibleactions(val) { |
| 93 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 } |
| 96 if (val) |
| 97 this._requestVisibleActions = val; |
| 98 }, |
| 99 |
| 100 /** <google-js-api> */ |
| 101 _apiLoader: null, |
| 102 |
| 103 /** an array of wanted scopes. oauth2 argument */ |
| 104 _requestedScopeArray: [], |
| 105 |
| 106 /** _requestedScopeArray as string */ |
| 107 get requestedScopes() { |
| 108 return this._requestedScopeArray.join(' '); |
| 109 }, |
| 110 |
| 111 /** Is user signed in? */ |
| 112 _signedIn: false, |
| 113 |
| 114 /** Currently granted scopes */ |
| 115 _grantedScopeArray: [], |
| 116 |
| 117 /** True if additional authorization is required */ |
| 118 _needAdditionalAuth: true, |
| 119 |
| 120 /** True if have google+ scopes */ |
| 121 _hasPlusScopes: false, |
| 122 |
| 123 /** |
| 124 * array of <google-signin-aware> |
| 125 * state changes are broadcast to them |
| 126 */ |
| 127 signinAwares: [], |
| 128 |
| 129 init: function() { |
| 130 this._apiLoader = document.createElement('google-js-api'); |
| 131 this._apiLoader.addEventListener('js-api-load', this.loadAuth2.bind(this
)); |
| 132 }, |
| 133 |
| 134 loadAuth2: function() { |
| 135 gapi.load('auth2', this.initAuth2.bind(this)); |
| 136 }, |
| 137 |
| 138 initAuth2: function() { |
| 139 if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId)
{ |
| 140 return; |
| 141 } |
| 142 var auth = gapi.auth2.init({ |
| 143 'client_id': this.clientId, |
| 144 'cookie_policy': this.cookiePolicy, |
| 145 'scope': this.requestedScopes |
| 146 }); |
| 147 |
| 148 auth.currentUser.listen(this.handleUserUpdate.bind(this)); |
| 149 |
| 150 auth.then( |
| 151 function success() { |
| 152 // Let the current user listener trigger the changes. |
| 153 }, |
| 154 function error(error) { |
| 155 console.error(error); |
| 156 } |
| 157 ); |
| 158 }, |
| 159 |
| 160 handleUserUpdate: function(newPrimaryUser) { |
| 161 // update and broadcast currentUser |
| 162 var isSignedIn = newPrimaryUser.isSignedIn(); |
| 163 if (isSignedIn != this._signedIn) { |
| 164 this._signedIn = isSignedIn; |
| 165 for (var i=0; i<this.signinAwares.length; i++) { |
| 166 this.signinAwares[i]._setSignedIn(isSignedIn); |
| 167 } |
| 168 } |
| 169 |
| 170 // update granted scopes |
| 171 this._grantedScopeArray = this.strToScopeArray( |
| 172 newPrimaryUser.getGrantedScopes()); |
| 173 // console.log(this._grantedScopeArray); |
| 174 this.updateAdditionalAuth(); |
| 175 |
| 176 var response = newPrimaryUser.getAuthResponse(); |
| 177 for (var i=0; i<this.signinAwares.length; i++) { |
| 178 this.signinAwares[i]._updateScopeStatus(response); |
| 179 } |
| 180 }, |
| 181 |
| 182 /** convert scope string to scope array */ |
| 183 strToScopeArray: function(str) { |
| 184 if (!str) { |
| 185 return []; |
| 186 } |
| 187 // remove extra spaces, then split |
| 188 var scopes = str.replace(/\ +/g, ' ').trim().split(' '); |
| 189 for (var i=0; i<scopes.length; i++) { |
| 190 scopes[i] = scopes[i].toLowerCase(); |
| 191 // Handle scopes that will be deprecated but are still returned with
their old value |
| 192 if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.profile')
{ |
| 193 scopes[i] = 'profile'; |
| 194 } |
| 195 if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.email') { |
| 196 scopes[i] = 'email'; |
| 197 } |
| 198 } |
| 199 // return with duplicates filtered out |
| 200 return scopes.filter( function(value, index, self) { |
| 201 return self.indexOf(value) === index; |
| 202 }); |
| 203 }, |
| 204 |
| 205 /** true if scopes have google+ scopes */ |
| 206 isPlusScope: function(scope) { |
| 207 return (scope.indexOf('/auth/games') > -1) |
| 208 || (scope.indexOf('auth/plus.') > -1 && scope.indexOf('auth/plus.me'
) < 0); |
| 209 }, |
| 210 |
| 211 /** true if scopes have been granted */ |
| 212 hasGrantedScopes: function(scopeStr) { |
| 213 var scopes = this.strToScopeArray(scopeStr); |
| 214 for (var i=0; i< scopes.length; i++) { |
| 215 if (this._grantedScopeArray.indexOf(scopes[i]) === -1) |
| 216 return false; |
| 217 } |
| 218 return true; |
| 219 }, |
| 220 |
| 221 /** request additional scopes */ |
| 222 requestScopes: function(newScopeStr) { |
| 223 var newScopes = this.strToScopeArray(newScopeStr); |
| 224 var scopesUpdated = false; |
| 225 for (var i=0; i<newScopes.length; i++) { |
| 226 if (this._requestedScopeArray.indexOf(newScopes[i]) === -1) { |
| 227 this._requestedScopeArray.push(newScopes[i]); |
| 228 scopesUpdated = true; |
| 229 } |
| 230 } |
| 231 if (scopesUpdated) { |
| 232 this.updateAdditionalAuth(); |
| 233 this.updatePlusScopes(); |
| 234 } |
| 235 }, |
| 236 |
| 237 /** update status of _needAdditionalAuth */ |
| 238 updateAdditionalAuth: function() { |
| 239 var needMoreAuth = false; |
| 240 for (var i=0; i<this._requestedScopeArray.length; i++) { |
| 241 if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) ===
-1) { |
| 242 needMoreAuth = true; |
| 243 break; |
| 244 } |
| 245 } |
| 246 if (this._needAdditionalAuth != needMoreAuth) { |
| 247 this._needAdditionalAuth = needMoreAuth; |
| 248 // broadcast new value |
| 249 for (var i=0; i<this.signinAwares.length; i++) { |
| 250 this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth); |
| 251 } |
| 252 } |
| 253 }, |
| 254 |
| 255 updatePlusScopes: function() { |
| 256 var hasPlusScopes = false; |
| 257 for (var i = 0; i < this._requestedScopeArray.length; i++) { |
| 258 if (this.isPlusScope(this._requestedScopeArray[i])) { |
| 259 hasPlusScopes = true; |
| 260 break; |
| 261 } |
| 262 } |
| 263 if (this._hasPlusScopes != hasPlusScopes) { |
| 264 this._hasPlusScopes = hasPlusScopes; |
| 265 for (var i=0; i<this.signinAwares.length; i++) { |
| 266 this.signinAwares[i]._setHasPlusScopes(hasPlusScopes); |
| 267 } |
| 268 } |
| 269 }, |
| 270 /** |
| 271 * attached <google-signin-aware> |
| 272 * @param {<google-signin-aware>} aware element to add |
| 273 */ |
| 274 attachSigninAware: function(aware) { |
| 275 if (this.signinAwares.indexOf(aware) == -1) { |
| 276 this.signinAwares.push(aware); |
| 277 // Initialize aware properties |
| 278 aware._setNeedAdditionalAuth(this._needAdditionalAuth); |
| 279 aware._setSignedIn(this._signedIn); |
| 280 aware._setHasPlusScopes(this._hasPlusScopes); |
| 281 } else { |
| 282 console.warn('signinAware attached more than once', aware); |
| 283 } |
| 284 }, |
| 285 |
| 286 detachSigninAware: function(aware) { |
| 287 var index = this.signinAwares.indexOf(aware); |
| 288 if (index != -1) { |
| 289 this.signinAwares.splice(index, 1); |
| 290 } else { |
| 291 console.warn('Trying to detach unattached signin-aware'); |
| 292 } |
| 293 }, |
| 294 |
| 295 /** returns scopes not granted */ |
| 296 getMissingScopes: function() { |
| 297 return this._requestedScopeArray.filter( function(scope) { |
| 298 return this._grantedScopeArray.indexOf(scope) === -1; |
| 299 }.bind(this)).join(' '); |
| 300 }, |
| 301 |
| 302 assertAuthInitialized: function() { |
| 303 if (!this.clientId) { |
| 304 throw new Error("AuthEngine not initialized. clientId has not been con
figured."); |
| 305 } |
| 306 if (!('gapi' in window)) { |
| 307 throw new Error("AuthEngine not initialized. gapi has not loaded."); |
| 308 } |
| 309 if (!('auth2' in window.gapi)) { |
| 310 throw new Error("AuthEngine not initialized. auth2 not loaded."); |
| 311 } |
| 312 }, |
| 313 |
| 314 /** pops up sign-in dialog */ |
| 315 signIn: function() { |
| 316 this.assertAuthInitialized(); |
| 317 var params = { |
| 318 'scope': this.getMissingScopes() |
| 319 }; |
| 320 |
| 321 // Proxy specific attributes through to the signIn options. |
| 322 Object.keys(ProxyLoginAttributes).forEach(function(key) { |
| 323 if (this[key] && this[key] !== '') { |
| 324 params[ProxyLoginAttributes[key]] = this[key]; |
| 325 } |
| 326 }, this); |
| 327 |
| 328 var promise; |
| 329 var user = gapi.auth2.getAuthInstance().currentUser.get(); |
| 330 if (user.getGrantedScopes()) { |
| 331 // additional auth, skip multiple account dialog |
| 332 promise = user.grant(params); |
| 333 } else { |
| 334 // initial signin |
| 335 promise = gapi.auth2.getAuthInstance().signIn(params); |
| 336 } |
| 337 promise.then( |
| 338 function success(newUser) { |
| 339 var authResponse = newUser.getAuthResponse(); |
| 340 // Let the current user listener trigger the changes. |
| 341 }, |
| 342 function error(error) { |
| 343 if ("Access denied." == error.reason) { |
| 344 // Access denied is not an error, user hit cancel |
| 345 return; |
| 346 } else { |
| 347 console.error(error); |
| 348 } |
| 349 } |
| 350 ); |
| 351 }, |
| 352 |
| 353 /** signs user out */ |
| 354 signOut: function() { |
| 355 this.assertAuthInitialized(); |
| 356 gapi.auth2.getAuthInstance().signOut().then( |
| 357 function success() { |
| 358 // Let the current user listener trigger the changes. |
| 359 }, |
| 360 function error(error) { |
| 361 console.error(error); |
| 362 } |
| 363 ); |
| 364 } |
| 365 }; |
| 366 |
| 367 AuthEngine.init(); |
| 368 |
| 369 /** |
| 370 `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 |
| 372 on the page. |
| 373 |
| 374 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). |
| 376 |
| 377 The `google-signin-aware-success` event is triggered when a user successfully |
| 378 authenticates. The `google-signin-aware-signed-out` event is triggered |
| 379 when a user explicitely signs out via the google-signin element. |
| 380 |
| 381 You can bind to `isAuthorized` property to monitor authorization state. |
| 382 ##### Example |
| 383 |
| 384 <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google
-signin-aware> |
| 385 */ |
| 386 Polymer({ |
| 387 |
| 388 is: 'google-signin-aware', |
| 389 |
| 390 /** |
| 391 * Fired when this scope has been authorized |
| 392 * @param {Object} result Authorization result. |
| 393 * @event google-signin-aware-success |
| 394 */ |
| 395 /** |
| 396 * Fired when this scope is not authorized |
| 397 * @event google-signin-aware-signed-out |
| 398 */ |
| 399 properties: { |
| 400 /** |
| 401 * App package name for android over-the-air installs. |
| 402 * See the relevant [docs](https://developers.google.com/+/web/signin/an
droid-app-installs) |
| 403 */ |
| 404 appPackageName: { |
| 405 type: String, |
| 406 observer: '_appPackageNameChanged' |
| 407 }, |
| 408 /** |
| 409 * a Google Developers clientId reference |
| 410 */ |
| 411 clientId: { |
| 412 type: String, |
| 413 observer: '_clientIdChanged' |
| 414 }, |
| 415 |
| 416 /** |
| 417 * The cookie policy defines what URIs have access to the session cookie |
| 418 * remembering the user's sign-in state. |
| 419 * See the relevant [docs](https://developers.google.com/+/web/signin/re
ference#determining_a_value_for_cookie_policy) for more information. |
| 420 * @default 'single_host_origin' |
| 421 */ |
| 422 cookiePolicy: { |
| 423 type: String, |
| 424 observer: '_cookiePolicyChanged' |
| 425 }, |
| 426 |
| 427 /** |
| 428 * The app activity types you want to write on behalf of the user |
| 429 * (e.g http://schemas.google.com/AddActivity) |
| 430 * |
| 431 */ |
| 432 requestVisibleActions: { |
| 433 type: String, |
| 434 observer: '_requestVisibleActionsChanged' |
| 435 }, |
| 436 |
| 437 /** |
| 438 * The scopes to provide access to (e.g https://www.googleapis.com/auth/
drive) |
| 439 * and should be space-delimited. |
| 440 */ |
| 441 scopes: { |
| 442 type: String, |
| 443 value: 'profile', |
| 444 observer: '_scopesChanged' |
| 445 }, |
| 446 |
| 447 /** |
| 448 * True if user is signed in |
| 449 */ |
| 450 signedIn: { |
| 451 type: Boolean, |
| 452 notify: true, |
| 453 readOnly: true |
| 454 }, |
| 455 |
| 456 /** |
| 457 * True if authorizations for *this* element have been granted |
| 458 */ |
| 459 isAuthorized: { |
| 460 type: Boolean, |
| 461 notify: true, |
| 462 readOnly: true, |
| 463 value: false |
| 464 }, |
| 465 |
| 466 /** |
| 467 * True if additional authorizations for *any* element are required |
| 468 */ |
| 469 needAdditionalAuth: { |
| 470 type: Boolean, |
| 471 notify: true, |
| 472 readOnly: true |
| 473 }, |
| 474 |
| 475 /** |
| 476 * True if *any* element has google+ scopes |
| 477 */ |
| 478 hasPlusScopes: { |
| 479 type: Boolean, |
| 480 value: false, |
| 481 notify: true, |
| 482 readOnly: true |
| 483 } |
| 484 }, |
| 485 |
| 486 attached: function() { |
| 487 AuthEngine.attachSigninAware(this); |
| 488 }, |
| 489 |
| 490 detached: function() { |
| 491 AuthEngine.detachSigninAware(this); |
| 492 }, |
| 493 |
| 494 /** pops up the authorization dialog */ |
| 495 signIn: function() { |
| 496 AuthEngine.signIn(); |
| 497 }, |
| 498 |
| 499 /** signs user out */ |
| 500 signOut: function() { |
| 501 AuthEngine.signOut(); |
| 502 }, |
| 503 |
| 504 _appPackageNameChanged: function(newName, oldName) { |
| 505 AuthEngine.appPackageName = newName; |
| 506 }, |
| 507 |
| 508 _clientIdChanged: function(newId, oldId) { |
| 509 AuthEngine.clientId = newId; |
| 510 }, |
| 511 |
| 512 _cookiePolicyChanged: function(newPolicy, oldPolicy) { |
| 513 AuthEngine.cookiePolicy = newPolicy; |
| 514 }, |
| 515 |
| 516 _requestVisibleActionsChanged: function(newVal, oldVal) { |
| 517 AuthEngine.requestVisibleActions = newVal; |
| 518 }, |
| 519 |
| 520 _scopesChanged: function(newVal, oldVal) { |
| 521 AuthEngine.requestScopes(newVal); |
| 522 this._updateScopeStatus(); |
| 523 }, |
| 524 |
| 525 _updateScopeStatus: function(user) { |
| 526 var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.sc
opes); |
| 527 if (newAuthorized !== this.isAuthorized) { |
| 528 this._setIsAuthorized(newAuthorized); |
| 529 if (newAuthorized) { |
| 530 this.fire('google-signin-aware-success', user); |
| 531 } |
| 532 else { |
| 533 this.fire('google-signin-aware-signed-out', user); |
| 534 } |
| 535 } |
| 536 } |
| 537 }); |
| 538 })(); |
| 539 </script> |
OLD | NEW |