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&lt;google-signin&gt; 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