1<!-- 2Copyright 2014 Google Inc 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 https://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15--> 16<link rel="import" href="../polymer/polymer.html"> 17<link rel="import" href="google-signin-aware.html"> 18<link rel="import" href="../iron-icon/iron-icon.html"> 19<link rel="import" href="../font-roboto/roboto.html"> 20<link rel="import" href="../google-apis/google-js-api.html"> 21<link rel="import" href="../paper-ripple/paper-ripple.html"> 22<link rel="import" href="../paper-material/paper-material.html"> 23<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html"> 24<link rel="import" href="google-icons.html"> 25<link rel="import" href="google-signin-styles.html"> 26 27<dom-module id="google-signin"> 28 <template> 29 <style include="google-signin-styles iron-positioning"></style> 30 31 <google-signin-aware id="aware" 32 app-package-name="{{appPackageName}}" 33 client-id="{{clientId}}" 34 cookie-policy="{{cookiePolicy}}" 35 request-visible-actions="{{requestVisibleActions}}" 36 hosted-domain="{{hostedDomain}}" 37 offline="{{offline}}" 38 offline-always-prompt="{{offlineAlwaysPrompt}}" 39 scopes="{{scopes}}" 40 openid-prompt="{{openidPrompt}}" 41 initialized="{{initialized}}" 42 signed-in="{{signedIn}}" 43 is-authorized="{{isAuthorized}}" 44 need-additional-auth="{{needAdditionalAuth}}" 45 has-plus-scopes="{{hasPlusScopes}}"></google-signin-aware> 46 <template is="dom-if" if="{{raised}}"> 47 <paper-material id="shadow" class="fit" elevation="2" animated></paper-material> 48 </template> 49 <div id="button" 50 class$="[[_computeButtonClass(height, width, theme, signedIn, _brand, needAdditionalAuth)]]"> 51 52 <paper-ripple id="ripple" class="fit"></paper-ripple> 53 <!-- this div is needed to position the ripple behind text content --> 54 <div> 55 <template is="dom-if" if="{{_computeButtonIsSignIn(signedIn, needAdditionalAuth)}}"> 56 <div class="button-content signIn" tabindex="0" 57 on-click="signIn" on-keydown="_signInKeyPress"> 58 <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> 59 <span class="buttonText">{{_labelSignin}}</span> 60 </div> 61 </template> 62 <template is="dom-if" if="{{_computeButtonIsSignOut(signedIn, needAdditionalAuth) }}"> 63 <div class="button-content signOut" tabindex="0" 64 on-click="signOut" on-keydown="_signOutKeyPress"> 65 <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> 66 <span class="buttonText">{{labelSignout}}</span> 67 </div> 68 </template> 69 <template is="dom-if" if="{{_computeButtonIsSignOutAddl(signedIn, needAdditionalAuth) }}"> 70 <div class="button-content signIn" tabindex="0" 71 on-click="signIn" on-keydown="_signInKeyPress"> 72 <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> 73 <span class="buttonText">{{labelAdditional}}</span> 74 </div> 75 </template> 76 </div> 77 78 </div> 79 </template> 80</dom-module> 81<script> 82 (function() { 83 84 /** 85 * Enum brand values. 86 * @readonly 87 * @enum {string} 88 */ 89 var BrandValue = { 90 GOOGLE: 'google', 91 PLUS: 'google-plus' 92 }; 93 94 /** 95 * Enum height values. 96 * @readonly 97 * @enum {string} 98 */ 99 var HeightValue = { 100 SHORT: 'short', 101 STANDARD: 'standard', 102 TALL: 'tall' 103 }; 104 105 /** 106 * Enum button label default values. 107 * @readonly 108 * @enum {string} 109 */ 110 var LabelValue = { 111 STANDARD: 'Sign in', 112 WIDE: 'Sign in with Google', 113 WIDE_PLUS: 'Sign in with Google+' 114 }; 115 116 /** 117 * Enum theme values. 118 * @readonly 119 * @enum {string} 120 */ 121 var ThemeValue = { 122 LIGHT: 'light', 123 DARK: 'dark' 124 }; 125 126 /** 127 * Enum width values. 128 * @readonly 129 * @enum {string} 130 */ 131 var WidthValue = { 132 ICON_ONLY: 'iconOnly', 133 STANDARD: 'standard', 134 WIDE: 'wide' 135 }; 136 137/** 138<google-signin> is used to authenticate with Google, allowing you to interact 139with other Google APIs such as Drive and Google+. 140 141<img style="max-width:100%;" src="https://cloud.githubusercontent.com/assets/107076/6791176/5c868822-d16a-11e4-918c-ec9b84a2db45.png"/> 142 143If you do not need to show the button, use companion `<google-signin-aware>` element to declare scopes, check authentication state. 144 145#### Examples 146 147 <google-signin client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> 148 149 <google-signin label-signin="Sign-in" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> 150 151 <google-signin theme="dark" width="iconOnly" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> 152 153 154#### Notes 155 156The attribute `clientId` is provided in your Google Developers Console 157(https://console.developers.google.com). 158 159The `scopes` attribute allows you to specify which scope permissions are required 160(e.g do you want to allow interaction with the Google Drive API). Many APIs also 161need to be enabled in the Google Developers Console before you can use them. 162 163The `requestVisibleActions` attribute is necessary if you want to write app 164activities (https://developers.google.com/+/web/app-activities/) on behalf of 165the user. Please note that this attribute is only valid in combination with the 166plus.login scope (https://www.googleapis.com/auth/plus.login). 167 168The `offline` attribute allows you to get an auth code which your server can 169redeem for an offline access token 170(https://developers.google.com/identity/sign-in/web/server-side-flow). 171You can also set `offline-always-prompt` instead of `offline` to ensure that your app 172will re-prompt the user for offline access and generate a working `refresh_token` 173even if they have already granted offline access to your app in the past. 174 175Use label properties to customize prompts. 176 177The button can be styled in using the `height`, `width`, and `theme` attributes. 178These attributes help you follow the Google+ Sign-In button branding guidelines 179(https://developers.google.com/+/branding-guidelines). 180 181The `google-signin-success` event is triggered when a user successfully authenticates 182and `google-signed-out` is triggered when user signs out. 183You can also use `isAuthorized` attribute to observe user's authentication state. 184 185Additional events, such as `google-signout-attempted` are 186triggered when the user attempts to sign-out and successfully signs out. 187 188When requesting offline access, the `google-signin-offline-success` event is 189triggered when the user successfully consents with offline support. 190 191The `google-signin-necessary` event is fired when scopes requested via 192google-signin-aware elements require additional user permissions. 193 194#### Testing 195 196By default, the demo accompanying this element is setup to work on localhost with 197port 8080. That said, you *should* update the `clientId` to your own one for 198any apps you're building. See the Google Developers Console 199(https://console.developers.google.com) for more info. 200 201@demo 202*/ 203 204 Polymer({ 205 206 is: 'google-signin', 207 208 /** 209 * Fired when user is signed in. 210 * You can use auth2 api to retrieve current user: `gapi.auth2.getAuthInstance()['currentUser'].get();` 211 * @event google-signin-success 212 */ 213 214 /** 215 * Fired when the user is signed-out. 216 * @event google-signed-out 217 */ 218 219 /** 220 * Fired if user requires additional authorization 221 * @event google-signin-necessary 222 */ 223 224 /** 225 * Fired when signed in, and scope has been authorized 226 * @param {Object} result Authorization result. 227 * @event google-signin-aware-success 228 */ 229 230 /** 231 * Fired when there is an error during the signin flow. 232 * @param {Object} detail The error object returned from the OAuth 2 flow. 233 * @event google-signin-aware-error 234 */ 235 236 /** 237 * Fired when an offline authorization is successful. 238 * @param {{code: string}} detail - 239 * code: The one-time authorization code from Google. 240 * Your application can exchange this for an `access_token` and `refresh_token` 241 * @event google-signin-offline-success 242 */ 243 244 /** 245 * This block is needed so the previous @param is not assigned to the next property. 246 */ 247 248 properties: { 249 /** 250 * App package name for android over-the-air installs. 251 * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs) 252 */ 253 appPackageName: { 254 type: String, 255 value: '' 256 }, 257 258 /** 259 * The brand being used for logo and styling. 260 * 261 * @default 'google' 262 */ 263 brand: { 264 type: String, 265 value: '' 266 }, 267 268 /** @private */ 269 _brand: { 270 type: String, 271 computed: '_computeBrand(brand, hasPlusScopes)' 272 }, 273 274 /** 275 * a Google Developers clientId reference 276 */ 277 clientId: { 278 type: String, 279 value: '' 280 }, 281 282 /** 283 * The cookie policy defines what URIs have access to the session cookie 284 * remembering the user's sign-in state. 285 * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information. 286 * 287 * @default 'single_host_origin' 288 */ 289 cookiePolicy: { 290 type: String, 291 value: '' 292 }, 293 294 /** 295 * The height to use for the button. 296 * 297 * Available options: short, standard, tall. 298 * 299 * @type {string} 300 */ 301 height: { 302 type: String, 303 value: 'standard' 304 }, 305 306 /** 307 * By default the ripple expands to fill the button. Set this to true to 308 * constrain the ripple to a circle within the button. 309 */ 310 fill: { 311 type: Boolean, 312 value: true 313 }, 314 315 /** 316 * An optional label for the button for additional permissions. 317 */ 318 labelAdditional: { 319 type: String, 320 value: 'Additional permissions required' 321 }, 322 323 /** 324 * An optional label for the sign-in button. 325 */ 326 labelSignin: { 327 type: String, 328 value: '' 329 }, 330 331 _labelSignin: { 332 type: String, 333 computed: '_computeSigninLabel(labelSignin, width, _brand)' 334 }, 335 336 /** 337 * An optional label for the sign-out button. 338 */ 339 labelSignout: { 340 type: String, 341 value: 'Sign out' 342 }, 343 344 /** 345 * If true, the button will be styled with a shadow. 346 */ 347 raised: { 348 type: Boolean, 349 value: false 350 }, 351 352 /** 353 * The app activity types you want to write on behalf of the user 354 * (e.g http://schemas.google.com/AddActivity) 355 */ 356 requestVisibleActions: { 357 type: String, 358 value: '' 359 }, 360 361 /** 362 * The Google Apps domain to which users must belong to sign in. 363 * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information. 364 */ 365 hostedDomain: { 366 type: String, 367 value: '' 368 }, 369 370 /** 371 * Allows for offline `access_token` retrieval during the signin process. 372 */ 373 offline: { 374 type: Boolean, 375 value: false 376 }, 377 378 /** 379 * Forces a re-prompt, even if the user has already granted offline 380 * access to your application in the past. You only need one of 381 * `offline` and `offlineAlwaysPrompt`. 382 */ 383 offlineAlwaysPrompt: { 384 type: Boolean, 385 value: false 386 }, 387 388 /** 389 * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive) 390 * and should be space-delimited. 391 */ 392 scopes: { 393 type: String, 394 value: '' 395 }, 396 397 /** 398 * Space-delimited, case-sensitive list of strings that 399 * specifies whether the the user is prompted for reauthentication 400 * and/or consent. The defined values are: 401 * none: do not display authentication or consent pages. 402 * This value is mutually exclusive with the rest. 403 * login: always prompt the user for reauthentication. 404 * consent: always show consent screen. 405 * select_account: always show account selection page. 406 * This enables a user who has multiple accounts to select amongst 407 * the multiple accounts that they might have current sessions for. 408 * For more information, see "prompt" parameter description in 409 * https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters 410 */ 411 openidPrompt: { 412 type: String, 413 value: '' 414 }, 415 416 /** 417 * The theme to use for the button. 418 * 419 * Available options: light, dark. 420 * 421 * @attribute theme 422 * @type {string} 423 * @default 'dark' 424 */ 425 theme: { 426 type: String, 427 value: 'light' 428 }, 429 430 /** 431 * The width to use for the button. 432 * 433 * Available options: iconOnly, standard, wide. 434 * 435 * @type {string} 436 */ 437 width: { 438 type: String, 439 value: 'standard' 440 }, 441 442 _brandIcon: { 443 type: String, 444 computed: '_computeIcon(_brand)' 445 }, 446 447 /** 448 * True if *any* element has google+ scopes 449 */ 450 hasPlusScopes: { 451 type: Boolean, 452 notify: true, 453 value: false 454 }, 455 456 /** 457 * True if additional authorization required globally 458 */ 459 needAdditionalAuth: { 460 type: Boolean, 461 notify: true, 462 value: false 463 }, 464 465 /** 466 * True when the auth library has been initialized, and signedIn property value is set from the first api response. 467 */ 468 initialized: { 469 type: Boolean, 470 notify: true, 471 value: false 472 }, 473 474 /** 475 * Is user signed in? 476 */ 477 signedIn: { 478 type: Boolean, 479 notify: true, 480 value: false, 481 observer: '_observeSignedIn' 482 }, 483 484 /** 485 * True if authorizations for *this* element have been granted 486 */ 487 isAuthorized: { 488 type: Boolean, 489 notify: true, 490 value: false 491 } 492 493 }, 494 495 _computeButtonClass: function(height, width, theme, signedIn, brand, needAdditionalAuth) { 496 return "height-" + height + " width-" + width + " theme-" + theme + " signedIn-" + signedIn + " brand-" + brand + " additionalAuth-" + needAdditionalAuth; 497 }, 498 499 _computeIcon: function(brand) { 500 return "google:" + brand; 501 }, 502 503 /* Button state computed */ 504 _computeButtonIsSignIn: function(signedIn, additionalAuth) { 505 return !signedIn; 506 }, 507 508 _computeButtonIsSignOut: function(signedIn, additionalAuth) { 509 return signedIn && !additionalAuth; 510 }, 511 512 _computeButtonIsSignOutAddl: function(signedIn, additionalAuth) { 513 return signedIn && additionalAuth; 514 }, 515 516 _computeBrand: function(attrBrand, hasPlusScopes) { 517 var newBrand; 518 if (attrBrand) { 519 newBrand = attrBrand; 520 } else if (hasPlusScopes) { 521 newBrand = BrandValue.PLUS; 522 } else { 523 newBrand = BrandValue.GOOGLE; 524 }; 525 return newBrand; 526 }, 527 528 _observeSignedIn: function(newVal, oldVal) { 529 if (newVal) { 530 if (this.needAdditionalAuth) { 531 this.fire('google-signin-necessary'); 532 } 533 this.fire('google-signin-success'); 534 } 535 // Use `oldVal` avoids to fire the event at the initialization of 536 // `signedIn`. 537 else if (oldVal) { 538 this.fire('google-signed-out'); 539 } 540 }, 541 542 /** 543 * Determines the proper label based on the attributes. 544 */ 545 _computeSigninLabel: function(labelSignin, width, _brand) { 546 if (labelSignin) { 547 return labelSignin; 548 } else { 549 switch(width) { 550 551 case WidthValue.WIDE: 552 return (_brand == BrandValue.PLUS) ? 553 LabelValue.WIDE_PLUS : LabelValue.WIDE; 554 555 case WidthValue.STANDARD: 556 return LabelValue.STANDARD; 557 558 case WidthValue.ICON_ONLY: 559 return ''; 560 561 default: 562 console.warn("bad width value: ", width); 563 return LabelValue.STANDARD; 564 } 565 } 566 }, 567 568 /** Sign in user. Opens the authorization dialog for signing in. 569 * The dialog will be blocked by a popup blocker unless called inside click handler. 570 */ 571 signIn: function () { 572 this.$.aware.signIn(); 573 }, 574 575 _signInKeyPress: function (e) { 576 if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) { 577 e.preventDefault(); 578 this.signIn(); 579 } 580 }, 581 582 /** Sign out the user */ 583 signOut: function () { 584 this.fire('google-signout-attempted'); 585 this.$.aware.signOut(); 586 }, 587 588 _signOutKeyPress: function (e) { 589 if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) { 590 e.preventDefault(); 591 this.signOut(); 592 } 593 } 594 }); 595 }()); 596</script> 597