diff --git a/frontend/src/metabase/Routes.jsx b/frontend/src/metabase/Routes.jsx
index 32a3f2587412dcb165285cbc803b8c9febff5473..9275346f2ca105842ab8e1822a3ca724d2e22f46 100644
--- a/frontend/src/metabase/Routes.jsx
+++ b/frontend/src/metabase/Routes.jsx
@@ -8,6 +8,7 @@ import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx";
 import LoginApp from "metabase/auth/containers/LoginApp.jsx";
 import LogoutApp from "metabase/auth/containers/LogoutApp.jsx";
 import PasswordResetApp from "metabase/auth/containers/PasswordResetApp.jsx";
+import GoogleNoAccount from "metabase/auth/components/GoogleNoAccount.jsx";
 
 // main app containers
 import DashboardApp from "metabase/dashboard/containers/DashboardApp.jsx";
@@ -72,9 +73,10 @@ export default class Routes extends Component {
                 </Route>
 
                 <Route path="/auth/forgot_password" component={ForgotPasswordApp} />
-                <Route path="/auth/login" component={this._forwardProps(LoginApp, ["onChangeLocation"])} />
+                <Route path="/auth/login" component={this._forwardProps(LoginApp, ["onChangeLocation", "setSessionFn"])} />
                 <Route path="/auth/logout" component={this._forwardProps(LogoutApp, ["onChangeLocation"])} />
                 <Route path="/auth/reset_password/:token" component={this._forwardProps(PasswordResetApp, ["onChangeLocation"])} />
+                <Route path="/auth/google_no_mb_account" component={GoogleNoAccount} />
 
                 <Route path="/card/:cardId" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "broadcastEventFn", "updateUrl"])} />
 
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
index 909010a5fbf880efcd0c0769914d74ff54619dc6..946268410de4318aeb7f9c0cd05b395cbcef200b 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
@@ -67,7 +67,7 @@ export default class DatabaseEditApp extends Component {
 
                     { /* Sidebar Actions */ }
                     { database && database.id != null &&
-                        <div className="Grid-cell Cell--1of3" ng-if="database.id">
+                        <div className="Grid-cell Cell--1of3">
                             <div className="Actions  bordered rounded shadowed">
                                 <h3>Actions</h3>
                                 <div className="Actions-group">
diff --git a/frontend/src/metabase/admin/people/components/AdminPeople.jsx b/frontend/src/metabase/admin/people/components/AdminPeople.jsx
index 5eecc70d6a1d8886edb28c88b5c926d0607aaf88..237533ea7f02f17fcac1eb2da3aa659832148e17 100644
--- a/frontend/src/metabase/admin/people/components/AdminPeople.jsx
+++ b/frontend/src/metabase/admin/people/components/AdminPeople.jsx
@@ -8,6 +8,8 @@ import Modal from "metabase/components/Modal.jsx";
 import ModalContent from "metabase/components/ModalContent.jsx";
 import PasswordReveal from "metabase/components/PasswordReveal.jsx";
 import UserAvatar from "metabase/components/UserAvatar.jsx";
+import Icon from "metabase/components/Icon.jsx";
+import Tooltip from "metabase/components/Tooltip.jsx";
 
 import EditUserForm from "./EditUserForm.jsx";
 import UserActionsSelect from "./UserActionsSelect.jsx";
@@ -364,6 +366,7 @@ export default class AdminPeople extends Component {
                             <thead>
                                 <tr>
                                     <th>Name</th>
+                                    <th></th>
                                     <th>Email</th>
                                     <th>Role</th>
                                     <th>Last Seen</th>
@@ -374,6 +377,12 @@ export default class AdminPeople extends Component {
                                 { users.map(user =>
                                 <tr>
                                     <td><span className="text-white inline-block"><UserAvatar background={(user.is_superuser) ? "bg-purple" : "bg-brand"} user={user} /></span> <span className="ml2 text-bold">{user.common_name}</span></td>
+                                    <td>
+                                      {user.google_auth ?
+                                        <Tooltip tooltip="Signed up via Google">
+                                            <Icon name='google' />
+                                        </Tooltip> : null}
+                                    </td>
                                     <td>{user.email}</td>
                                     <td>
                                         <UserRoleSelect
diff --git a/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c6606d9d0f325ff97e2f4c5290255b44de98bfd6
--- /dev/null
+++ b/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx
@@ -0,0 +1,143 @@
+import React, { Component, PropTypes } from "react";
+
+import cx from "classnames";
+import _ from "underscore";
+
+import Input from "metabase/components/Input.jsx";
+
+export default class SettingsSingleSignOnForm extends Component {
+    constructor(props, context) {
+        super(props, context);
+        this.updateClientID    = this.updateClientID.bind(this);
+        this.updateDomain      = this.updateDomain.bind(this);
+        this.onCheckboxClicked = this.onCheckboxClicked.bind(this),
+        this.saveChanges       = this.saveChanges.bind(this),
+        this.clientIDChanged   = this.clientIDChanged.bind(this),
+        this.domainChanged     = this.domainChanged.bind(this)
+    }
+
+    static propTypes = {
+        elements: PropTypes.array,
+        updateSetting: PropTypes.func.isRequired
+    };
+
+    componentWillMount() {
+        let { elements } = this.props,
+            clientID     = _.findWhere(elements, {key: 'google-auth-client-id'}),
+            domain       = _.findWhere(elements, {key: 'google-auth-auto-create-accounts-domain'});
+
+        this.setState({
+            clientID:      clientID,
+            domain:        domain,
+            clientIDValue: clientID.value,
+            domainValue:   domain.value,
+            recentlySaved: false
+        });
+    }
+
+    updateClientID(newValue) {
+        if (newValue === this.state.clientIDValue) return;
+
+        this.setState({
+            clientIDValue: newValue && newValue.length ? newValue : null,
+            recentlySaved: false
+        });
+    }
+
+    updateDomain(newValue) {
+        if (newValue === this.state.domain.value) return;
+
+        this.setState({
+            domainValue: newValue && newValue.length ? newValue : null,
+            recentlySaved: false
+        });
+    }
+
+    clientIDChanged() {
+        return this.state.clientID.value !== this.state.clientIDValue;
+    }
+
+    domainChanged() {
+        return this.state.domain.value !== this.state.domainValue;
+    }
+
+    saveChanges() {
+        let { clientID, clientIDValue, domain, domainValue } = this.state;
+
+        if (this.clientIDChanged()) {
+            this.props.updateSetting(clientID, clientIDValue);
+            this.setState({
+                clientID: {
+                    value: clientIDValue
+                },
+                recentlySaved: true
+            });
+        }
+
+        if (this.domainChanged()) {
+            this.props.updateSetting(domain, domainValue);
+            this.setState({
+                domain: {
+                    value: domainValue
+                },
+                recentlySaved: true
+            });
+        }
+    }
+
+    onCheckboxClicked() {
+        // if domain is present, clear it out; otherwise if there's no domain try to set it back to what it was
+        this.setState({
+            domainValue: this.state.domainValue ? null : this.state.domain.value,
+            recentlySaved: false
+        });
+    }
+
+    render() {
+        let hasChanges  = this.domainChanged() || this.clientIDChanged(),
+            hasClientID = this.state.clientIDValue;
+
+        return (
+            <form noValidate>
+                <div className="px2"
+                     style={{maxWidth: "585px"}}>
+                    <h2>Sign in with Google</h2>
+                    <p className="text-grey-4">
+                        Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password.
+                    </p>
+                    <p className="text-grey-4">
+                        To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found <a className="link" href="https://developers.google.com/identity/sign-in/web/devconsole-project" target="_blank">here.</a>
+                    </p>
+                    <Input
+                        className="SettingsInput AdminInput bordered rounded h3"
+                        type="text"
+                        value={this.state.clientIDValue}
+                        placeholder="Your Google client ID"
+                        onChange={(event) => this.updateClientID(event.target.value)}
+                    />
+                    <div className="py3">
+                        <div className="flex align-center">
+                            <p className="text-grey-4">Allow users to sign up on their own if their Google account email address is from:</p>
+                        </div>
+                        <div className="mt1 bordered rounded inline-block">
+                            <div className="inline-block px2 h2">@</div>
+                            <Input
+                                className="SettingsInput inline-block AdminInput h3 border-left"
+                                type="text"
+                                value={this.state.domainValue}
+                                onChange={(event) => this.updateDomain(event.target.value)}
+                                disabled={!hasClientID}
+                            />
+                        </div>
+                    </div>
+
+                    <button className={cx("Button mr2", {"Button--primary": hasChanges})}
+                            disabled={!hasChanges}
+                            onClick={this.saveChanges}>
+                        {this.state.recentlySaved ? "Changes saved!" : "Save Changes"}
+                    </button>
+                </div>
+            </form>
+        );
+    }
+}
diff --git a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
index 43b13f1543c763bd5019bc66b1b79d0d57dc15bd..393b26ff60c64a52c2f8c964ac2f2908bfa466a0 100644
--- a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
+++ b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
@@ -8,6 +8,7 @@ import SettingsEmailForm from "../components/SettingsEmailForm.jsx";
 import SettingsSlackForm from "../components/SettingsSlackForm.jsx";
 import SettingsSetupList from "../components/SettingsSetupList.jsx";
 import SettingsUpdatesForm from "../components/SettingsUpdatesForm.jsx";
+import SettingsSingleSignOnForm from "../components/SettingsSingleSignOnForm.jsx";
 
 import _ from "underscore";
 import cx from 'classnames';
@@ -119,6 +120,15 @@ export default class SettingsEditorApp extends Component {
                     />
                 </div>
             );
+        } else if (section.name === "Single Sign On") {
+            return (
+                <div className="px2">
+                    <SettingsSingleSignOnForm
+                        elements={section.settings}
+                        updateSetting={this.updateSetting}
+                    />
+                </div>
+            );
         } else {
             let settings = section.settings.map((setting, index) => {
                 return <SettingsSetting key={setting.key} setting={setting} updateSetting={this.updateSetting} handleChangeEvent={this.handleChangeEvent} autoFocus={index === 0}/>
diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js
index d401593ba635dc012bbe7ebca9eb514633188f2c..3732ff95ab1720fcd9a7329359c65bba7d0018fe 100644
--- a/frontend/src/metabase/admin/settings/selectors.js
+++ b/frontend/src/metabase/admin/settings/selectors.js
@@ -126,6 +126,17 @@ const SECTIONS = [
                 type: "boolean"
             }
         ]
+    },
+    {
+        name: "Single Sign On",
+        settings: [
+            {
+                key: "google-auth-client-id"
+            },
+            {
+                key: "google-auth-auto-create-accounts-domain"
+            }
+        ]
     }
 ];
 
diff --git a/frontend/src/metabase/app.js b/frontend/src/metabase/app.js
index 6758f737e2a1410cab7d25e916306cd8b6445ab9..d3c5d43bd6ee27f1edc980346e12256494144e44 100644
--- a/frontend/src/metabase/app.js
+++ b/frontend/src/metabase/app.js
@@ -54,7 +54,7 @@ import * as setup from "metabase/setup/reducers";
 
 /* user */
 import * as user from "metabase/user/reducers";
-import { currentUser } from "metabase/user";
+import { currentUser, setUser } from "metabase/user";
 
 import { registerAnalyticsClickListener } from "metabase/lib/analytics";
 import { serializeCardForUrl, cleanCopyCard, urlForCardState } from "metabase/lib/card";
@@ -239,6 +239,14 @@ angular.module('metabase', [
         };
         $scope.store = createStoreWithAngularScope($scope, $location, reducers, {currentUser: AppState.model.currentUser});
 
+        // ANGULAR_HACKâ„¢: this seems like the easiest way to keep the redux store up to date with the currentUser :-/
+        let userSyncTimer = setInterval(() => {
+            if ($scope.store.getState().currentUser !== AppState.model.currentUser) {
+                $scope.store.dispatch(setUser(AppState.model.currentUser));
+            }
+        }, 250);
+        $scope.$on("$destroy", () => clearInterval(userSyncTimer));
+
         // HACK: prevent reloading controllers as the URL changes
         let route = $route.current;
         $scope.$on('$locationChangeSuccess', function (event) {
diff --git a/frontend/src/metabase/auth/auth.js b/frontend/src/metabase/auth/auth.js
index 10f9cd9ae01845e9930f241e17fa738a64dab22f..40f3bc15c1ec446ccb534f720df5c3f432cf0eaf 100644
--- a/frontend/src/metabase/auth/auth.js
+++ b/frontend/src/metabase/auth/auth.js
@@ -4,9 +4,11 @@ import { handleActions, combineReducers, AngularResourceProxy, createThunkAction
 import MetabaseCookies from "metabase/lib/cookies";
 import MetabaseUtils from "metabase/lib/utils";
 
+import { clearGoogleAuthCredentials } from "metabase/lib/auth";
+
 
 // resource wrappers
-const SessionApi = new AngularResourceProxy("Session", ["create", "delete", "reset_password"]);
+const SessionApi = new AngularResourceProxy("Session", ["create", "createWithGoogleAuth", "delete", "reset_password"]);
 
 
 // login
@@ -33,6 +35,34 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
     };
 });
 
+
+// login Google
+export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser, onChangeLocation) {
+    return async function(dispatch, getState) {
+        try {
+            let newSession = await SessionApi.createWithGoogleAuth({
+                token: googleUser.getAuthResponse().id_token
+            });
+
+            // since we succeeded, lets set the session cookie
+            MetabaseCookies.setSessionCookie(newSession.id);
+
+            // TODO: redirect after login (carry user to intended destination)
+            // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
+            setTimeout(() => onChangeLocation("/"), 300);
+
+        } catch (error) {
+            clearGoogleAuthCredentials();
+            // If we see a 428 ("Precondition Required") that means we need to show the "No Metabase account exists for this Google Account" page
+            if (error.status === 428) {
+                onChangeLocation('/auth/google_no_mb_account');
+            } else {
+                return error;
+            }
+        }
+    };
+});
+
 // logout
 export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation) {
     return async function(dispatch, getState) {
@@ -84,7 +114,8 @@ export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(t
 // reducers
 
 const loginError = handleActions({
-    ["AUTH_LOGIN"]: { next: (state, { payload }) => payload ? payload : null }
+    ["AUTH_LOGIN"]: { next: (state, { payload }) => payload ? payload : null },
+    ["AUTH_LOGIN_GOOGLE"]: { next: (state, { payload }) => payload ? payload : null }
 }, null);
 
 const resetSuccess = handleActions({
diff --git a/frontend/src/metabase/auth/components/AuthScene.jsx b/frontend/src/metabase/auth/components/AuthScene.jsx
index 01f0af83fcf58f17f9dea33597d3447d63270428..c67987f8b8c99f8d5a364c6977c3f5329aa29e82 100644
--- a/frontend/src/metabase/auth/components/AuthScene.jsx
+++ b/frontend/src/metabase/auth/components/AuthScene.jsx
@@ -10,7 +10,7 @@ export default class AuthScene extends Component {
 
     render() {
         return (
-            <section className="brand-scene absolute bottom left right hide sm-show">
+            <section className="z1 brand-scene absolute bottom left right hide sm-show">
                 <div className="brand-boat-container">
                     <svg className="brand-boat" width="27px" height="28px" viewBox="0 0 27 28">
                         <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd" ref="HACK_fill_rule_1">
diff --git a/frontend/src/metabase/auth/components/BackToLogin.jsx b/frontend/src/metabase/auth/components/BackToLogin.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4d602083b2b91dcc6750b175e0ed7d08183fb9a7
--- /dev/null
+++ b/frontend/src/metabase/auth/components/BackToLogin.jsx
@@ -0,0 +1,6 @@
+import React from 'react'
+
+const BackToLogin = () =>
+    <a className="link block" href="/auth/login">Back to login</a>
+
+export default BackToLogin;
diff --git a/frontend/src/metabase/auth/components/GoogleNoAccount.jsx b/frontend/src/metabase/auth/components/GoogleNoAccount.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fac7f9ae0e12c589b8ccbe3c669f18fdc8bf5e57
--- /dev/null
+++ b/frontend/src/metabase/auth/components/GoogleNoAccount.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+import AuthScene from "./AuthScene.jsx";
+import LogoIcon from "metabase/components/LogoIcon.jsx";
+import BackToLogin from "./BackToLogin.jsx"
+
+const GoogleNoAccount = () =>
+    <div className="full-height bg-white flex flex-column flex-full md-layout-centered">
+        <div className="wrapper">
+            <div className="Login-wrapper Grid  Grid--full md-Grid--1of2">
+                <div className="Grid-cell flex layout-centered text-brand">
+                    <LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
+                </div>
+                <div className="Grid-cell text-centered bordered rounded shadowed p4">
+                    <h3 className="mt4 mb2">No Metabase account exists for this Google account.</h3>
+                    <p className="mb4 ml-auto mr-auto" style={{maxWidth: 360}}>
+                        You'll need an administrator to create a Metabase account before
+                        you can use Google to log in.
+                    </p>
+
+                    <BackToLogin />
+                </div>
+            </div>
+        </div>
+        <AuthScene />
+    </div>
+
+export default GoogleNoAccount;
diff --git a/frontend/src/metabase/auth/components/SSOLoginButton.jsx b/frontend/src/metabase/auth/components/SSOLoginButton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..91cb3b72fd66e0756302b241495280754fb3c10a
--- /dev/null
+++ b/frontend/src/metabase/auth/components/SSOLoginButton.jsx
@@ -0,0 +1,26 @@
+import React, { Component, PropTypes } from "react";
+import Icon from "metabase/components/Icon";
+import { capitalize } from 'humanize'
+
+const propTypes = {
+  provider: PropTypes.string.isRequired
+};
+
+class SSOLoginButton extends Component {
+  render () {
+    const { provider } = this.props
+    return (
+        <div className="relative z2 bg-white p2 cursor-pointer shadow-hover text-centered sm-text-left rounded block sm-inline-block bordered shadowed">
+            <div className="flex align-center">
+                <Icon className="mr1" name={provider} />
+                <h4>{`Sign in with ${capitalize(provider)}`}</h4>
+            </div>
+        </div>
+    )
+  }
+}
+
+
+SSOLoginButton.proptypes = propTypes
+
+export default SSOLoginButton;
diff --git a/frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx b/frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx
index eb3b867baf513323919cdeff203ae120e43e1434..bbec60699e3a7a6d1b9ca48424ee7925da83236a 100644
--- a/frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx
+++ b/frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx
@@ -5,6 +5,7 @@ import _ from "underscore";
 import cx from "classnames";
 
 import AuthScene from "../components/AuthScene.jsx";
+import BackToLogin from "../components/BackToLogin.jsx"
 import FormField from "metabase/components/form/FormField.jsx";
 import FormLabel from "metabase/components/form/FormLabel.jsx";
 import FormMessage from "metabase/components/form/FormMessage.jsx";
@@ -55,7 +56,7 @@ export default class ForgotPasswordApp extends Component {
                       <div className="Grid-cell">
                           <div className="text-centered bordered rounded shadowed p4">
                               <h3 className="my4">Please contact an administrator to have them reset your password</h3>
-                              <a className="link block mb4" href="/auth/login">Back to login</a>
+                              <BackToLogin />
                           </div>
                       </div>
                       :
diff --git a/frontend/src/metabase/auth/containers/LoginApp.jsx b/frontend/src/metabase/auth/containers/LoginApp.jsx
index a58b36ee40cef70e99d946ba1d65ac67fc279aca..abc33827c51ecca1750b17415134996dae8ff5e1 100644
--- a/frontend/src/metabase/auth/containers/LoginApp.jsx
+++ b/frontend/src/metabase/auth/containers/LoginApp.jsx
@@ -1,13 +1,16 @@
 import React, { Component, PropTypes } from "react";
+import { findDOMNode } from "react-dom";
 import { connect } from "react-redux";
 
 import cx from "classnames";
 
 import AuthScene from "../components/AuthScene.jsx";
+import SSOLoginButton from "../components/SSOLoginButton.jsx";
 import FormField from "metabase/components/form/FormField.jsx";
 import FormLabel from "metabase/components/form/FormLabel.jsx";
 import FormMessage from "metabase/components/form/FormMessage.jsx";
 import LogoIcon from "metabase/components/LogoIcon.jsx";
+import Settings from "metabase/lib/settings.js";
 
 
 import * as authActions from "../auth";
@@ -51,13 +54,49 @@ export default class LoginApp extends Component {
     }
 
     componentDidMount() {
+
         this.validateForm();
+
+        const { loginGoogle, onChangeLocation } = this.props;
+
+        let ssoLoginButton = findDOMNode(this.refs.ssoLoginButton);
+
+        function attachGoogleAuth() {
+            // if gapi isn't loaded yet then wait 100ms and check again. Keep doing this until we're ready
+            if (!window.gapi) {
+                window.setTimeout(attachGoogleAuth, 100);
+                return;
+            }
+            try {
+                window.gapi.load('auth2', () => {
+                  let auth2 = window.gapi.auth2.init({
+                      client_id: Settings.get('google_auth_client_id'),
+                      cookiepolicy: 'single_host_origin',
+                  });
+                  auth2.attachClickHandler(ssoLoginButton, {},
+                      (googleUser) => loginGoogle(googleUser, onChangeLocation),
+                      (error) => console.error('There was an error logging in', error)
+                  );
+                })
+            } catch (error) {
+                console.error('Error attaching Google Auth handler: ', error);
+            }
+        }
+        attachGoogleAuth();
     }
 
     componentDidUpdate() {
         this.validateForm();
     }
 
+    componentWillReceiveProps(newProps) {
+        const { user, onChangeLocation } = newProps;
+            // if we already have a user then we shouldn't be logging in
+        if (user) {
+            onChangeLocation("/");
+        }
+    }
+
     onChange(fieldName, fieldValue) {
         this.setState({ credentials: { ...this.state.credentials, [fieldName]: fieldValue }});
     }
@@ -72,22 +111,28 @@ export default class LoginApp extends Component {
     }
 
     render() {
-        if (this.props.user) {
-            // if we already have a user then we shouldn't be logging in
-            this.props.onChangeLocation("/");
-        }
 
         const { loginError } = this.props;
 
         return (
             <div className="full-height full bg-white flex flex-column flex-full md-layout-centered">
-                <div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2">
+                <div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2 relative z2">
                     <div className="Grid-cell flex layout-centered text-brand">
                         <LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
                     </div>
                     <div className="Login-content Grid-cell">
                         <form className="Form-new bg-white bordered rounded shadowed" name="form" onSubmit={(e) => this.formSubmitted(e)} noValidate>
-                            <h3 className="Login-header Form-offset mb3">Sign in to Metabase</h3>
+                            <h3 className="Login-header Form-offset">Sign in to Metabase</h3>
+
+                            { Settings.ssoEnabled() &&
+                                <div className="mx4 mb4 py3 border-bottom relative">
+                                    <SSOLoginButton provider='google' ref="ssoLoginButton"/>
+                                    {/*<div className="g-signin2 ml1 relative z2" id="g-signin2"></div>*/}
+                                    <div className="mx1 absolute text-centered left right" style={{ bottom: -8 }}>
+                                        <span className="text-bold px3 py2 text-grey-3 bg-white">OR</span>
+                                    </div>
+                                </div>
+                            }
 
                             <FormMessage formError={loginError && loginError.data.message ? loginError : null} ></FormMessage>
 
@@ -105,7 +150,7 @@ export default class LoginApp extends Component {
 
                             <div className="Form-field">
                                 <ul className="Form-offset">
-                                    <input name="remember" type="checkbox" ng-init="remember_me = true" checked /> <label className="inline-block">Remember Me:</label>
+                                    <input name="remember" type="checkbox" defaultChecked /> <label className="inline-block">Remember Me:</label>
                                 </ul>
                             </div>
 
diff --git a/frontend/src/metabase/css/core/layout.css b/frontend/src/metabase/css/core/layout.css
index 98c94e2bd0cfa1956e296c34d4de99cee90c83e3..1636716bf5f9e41b0d0ed84ccd01376956ff450e 100644
--- a/frontend/src/metabase/css/core/layout.css
+++ b/frontend/src/metabase/css/core/layout.css
@@ -40,6 +40,10 @@
 .inline-block,
 :local(.inline-block) { display: inline-block; }
 
+@media screen and (--breakpoint-min-sm) {
+    .sm-inline-block { display: inline-block; }
+}
+
 .table { display: table; }
 
 .full, :local(.full) { width: 100%; }
diff --git a/frontend/src/metabase/css/core/shadow.css b/frontend/src/metabase/css/core/shadow.css
index 44628f01512da7f1967dc24d1ee0bbbbb640b9f0..961a72bc7d2238ed8a0b307e7685c8044689e99c 100644
--- a/frontend/src/metabase/css/core/shadow.css
+++ b/frontend/src/metabase/css/core/shadow.css
@@ -1,6 +1,12 @@
 :root {
     --shadow-color: rgba(0, 0, 0, .08);
+    --shadow-hover-color: rgba(0, 0, 0, .12);
 }
 .shadowed, :local(.shadowed) {
   box-shadow: 0 2px 2px var(--shadow-color);
 }
+
+.shadow-hover:hover {
+  box-shadow: 0 2px 2px var(--shadow-hover-color);
+  transition: box-shadow 300ms linear;
+}
diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js
index e6cc4f001d17c048f9970c79b36a991f91e18ed0..cb30929777e45b2df6e8bbbf5b1aed1fcb2840a2 100644
--- a/frontend/src/metabase/icon_paths.js
+++ b/frontend/src/metabase/icon_paths.js
@@ -71,6 +71,9 @@ export var ICON_PATHS = {
     folder: "M3.96901618e-15,5.41206355 L0.00949677904,29 L31.8821132,29 L31.8821132,10.8928571 L18.2224205,10.8928571 L15.0267944,5.41206355 L3.96901618e-15,5.41206355 Z M16.8832349,5.42402804 L16.8832349,4.52140947 C16.8832349,3.68115822 17.5639241,3 18.4024298,3 L27.7543992,3 L30.36417,3 C31.2031259,3 31.8832341,3.67669375 31.8832341,4.51317691 L31.8832341,7.86669975 L31.8832349,8.5999999 L18.793039,8.5999999 L16.8832349,5.42402804 Z",
     gear: 'M14 0 H18 L19 6 L20.707 6.707 L26 3.293 L28.707 6 L25.293 11.293 L26 13 L32 14 V18 L26 19 L25.293 20.707 L28.707 26 L26 28.707 L20.707 25.293 L19 26 L18 32 L14 32 L13 26 L11.293 25.293 L6 28.707 L3.293 26 L6.707 20.707 L6 19 L0 18 L0 14 L6 13 L6.707 11.293 L3.293 6 L6 3.293 L11.293 6.707 L13 6 L14 0 z M16 10 A6 6 0 0 0 16 22 A6 6 0 0 0 16 10',
     grid: 'M2 2 L10 2 L10 10 L2 10z M12 2 L20 2 L20 10 L12 10z M22 2 L30 2 L30 10 L22 10z M2 12 L10 12 L10 20 L2 20z M12 12 L20 12 L20 20 L12 20z M22 12 L30 12 L30 20 L22 20z M2 22 L10 22 L10 30 L2 30z M12 22 L20 22 L20 30 L12 30z M22 22 L30 22 L30 30 L22 30z',
+    google: {
+        svg: '<g fill="none" fill-rule="evenodd"><path d="M16 32c4.32 0 7.947-1.422 10.596-3.876l-5.05-3.91c-1.35.942-3.164 1.6-5.546 1.6-4.231 0-7.822-2.792-9.102-6.65l-5.174 4.018C4.356 28.41 9.742 32 16 32z" fill="#34A853"/><path d="M6.898 19.164A9.85 9.85 0 0 1 6.364 16c0-1.102.196-2.169.516-3.164L1.707 8.818A16.014 16.014 0 0 0 0 16c0 2.578.622 5.013 1.707 7.182l5.19-4.018z" fill="#FBBC05"/><path d="M31.36 16.356c0-1.316-.107-2.276-.338-3.272H16v5.938h8.818c-.178 1.476-1.138 3.698-3.271 5.191l5.049 3.911c3.022-2.79 4.764-6.897 4.764-11.768z" fill="#4285F4"/><path d="M16 6.187c3.004 0 5.031 1.297 6.187 2.382l4.515-4.409C23.93 1.582 20.32 0 16 0 9.742 0 4.338 3.591 1.707 8.818l5.173 4.018c1.298-3.858 4.889-6.65 9.12-6.65z" fill="#EA4335"/></g>'
+    },
     history: {
         path: "M35,16.5 C35,25.0368108 28.12452,32 19.69524,32 C15.4568,32 11.59542,30.2352712 8.8642,27.421691 L11.2187,25.0368108 C13.38484,27.1827209 16.3986,28.5659239 19.69524,28.5659239 C26.28818,28.5659239 31.60918,23.1770449 31.60918,16.5 C31.60918,9.82295508 26.28818,4.43373173 19.69524,4.43373173 C13.90266,4.43373173 9.05256,8.48761496 7.96932,14.0197383 L11.78378,13.4474497 L6.36826,21.1258275 L1,13.4474497 L4.57884,13.9718754 C5.709,6.57998623 12.0194,1 19.69524,1 C28.12452,1 35,7.91532634 35,16.5 Z M21.2212723,10.8189082 L21.1121095,17.4229437 L23.8197636,19.9536905 C25.5447498,21.5044111 23.0137118,23.8063921 21.4895987,22.3922785 L18.2842405,19.4460113 C17.9625843,19.1048495 17.7475101,18.7575877 17.7628492,18.3548451 L17.789933,10.8461204 C17.7725216,8.69014788 21.1413368,8.68773723 21.2212723,10.8189082 Z",
         attrs: { viewBox: "0 0 36 33" }
diff --git a/frontend/src/metabase/lib/auth.js b/frontend/src/metabase/lib/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3fdd444ac3fba2ca16be7d3e0002d5aa19e13e9
--- /dev/null
+++ b/frontend/src/metabase/lib/auth.js
@@ -0,0 +1,15 @@
+/*global gapi*/
+
+/// clear out Google Auth credentials in browser if present
+export function clearGoogleAuthCredentials() {
+    let googleAuth = typeof gapi !== 'undefined' && gapi && gapi.auth2 ? gapi.auth2.getAuthInstance() : undefined;
+    if (!googleAuth) return;
+
+    try {
+        googleAuth.signOut().then(function() {
+            console.log('Cleared Google Auth credentials.');
+        });
+    } catch (error) {
+        console.error('Problem clearing Google Auth credentials', error);
+    }
+}
diff --git a/frontend/src/metabase/lib/cookies.js b/frontend/src/metabase/lib/cookies.js
index abca458d607ac590570dfcf4df33b73fdc536467..fdfd9eca242d42e86b0943b74cb85621cc412d4c 100644
--- a/frontend/src/metabase/lib/cookies.js
+++ b/frontend/src/metabase/lib/cookies.js
@@ -1,3 +1,6 @@
+
+import { clearGoogleAuthCredentials } from "metabase/lib/auth";
+
 export const METABASE_SESSION_COOKIE = 'metabase.SESSION_ID';
 
 var mb_cookies = {};
@@ -29,8 +32,9 @@ var MetabaseCookies = {
         } else {
             sessionId = mb_cookies.ipCookie(METABASE_SESSION_COOKIE);
 
-            // delete the current session cookie
+            // delete the current session cookie and Google Auth creds
             mb_cookies.ipCookie.remove(METABASE_SESSION_COOKIE);
+            clearGoogleAuthCredentials();
 
             // send a logout notification event
             mb_cookies.scope.$broadcast('appstate:logout', sessionId);
diff --git a/frontend/src/metabase/lib/settings.js b/frontend/src/metabase/lib/settings.js
index afcde976bd4595354aea4ff80c2c4df992290a3b..dbd9dfe281c1ed2f6e16579e2e524275bdddd318 100644
--- a/frontend/src/metabase/lib/settings.js
+++ b/frontend/src/metabase/lib/settings.js
@@ -35,6 +35,10 @@ const MetabaseSettings = {
         return (mb_settings.setup_token !== undefined && mb_settings.setup_token !== null);
     },
 
+    ssoEnabled: function() {
+        return mb_settings.google_auth_client_id != null;
+    },
+
     newVersionAvailable: function(settings) {
         let versionInfo = _.findWhere(settings, {key: "version-info"}),
             currentVersion = MetabaseSettings.get("version").tag;
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index e535910118b0b50115f1fa490e37a0612f5e0011..5b2660c6b58357e0a08f615cbbc842cbb1ac925f 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -749,6 +749,11 @@ CoreServices.factory('Session', ['$resource', '$cookies', function($resource, $c
             method: 'POST',
             ignoreAuthModule: true // this ensures a 401 response doesn't trigger another auth-required event
         },
+        createWithGoogleAuth: {
+            url: '/api/session/google_auth',
+            method: 'POST',
+            ignoreAuthModule: true
+        },
         delete: {
             method: 'DELETE'
         },
diff --git a/frontend/src/metabase/setup/components/PreferencesStep.jsx b/frontend/src/metabase/setup/components/PreferencesStep.jsx
index 8d48931099e0a81e431c61600a32c915046fcdbf..95fac785d199cd8838534d81d83245b5f379cd21 100644
--- a/frontend/src/metabase/setup/components/PreferencesStep.jsx
+++ b/frontend/src/metabase/setup/components/PreferencesStep.jsx
@@ -69,10 +69,10 @@ export default class PreferencesStep extends Component {
                         : null }
 
                         <div className="Form-actions">
-                            <button className="Button Button--primary" ng-click="setUsagePreference()">
+                            <button className="Button Button--primary">
                                 Next
                             </button>
-                            <mb-form-message form="usageForm"></mb-form-message>
+                            { /* FIXME: <mb-form-message form="usageForm"></mb-form-message>*/ }
                         </div>
                     </form>
                 </section>
diff --git a/frontend/src/metabase/user/components/UpdateUserDetails.jsx b/frontend/src/metabase/user/components/UpdateUserDetails.jsx
index 98ff6202e2406daa1b1564a443575c934a10650f..f351d330a4007c7142f314ec55da9d518f290159 100644
--- a/frontend/src/metabase/user/components/UpdateUserDetails.jsx
+++ b/frontend/src/metabase/user/components/UpdateUserDetails.jsx
@@ -80,6 +80,7 @@ export default class UpdateUserDetails extends Component {
     render() {
         const { updateUserResult, user } = this.props;
         const { formError, valid } = this.state;
+        const managed = user.google_auth
 
         return (
             <div>
@@ -97,9 +98,23 @@ export default class UpdateUserDetails extends Component {
                     </FormField>
 
                     <FormField fieldName="email" formError={formError}>
-                        <FormLabel title="Email address" fieldName="email" formError={formError} ></FormLabel>
-                        <input ref="email" className="Form-input Form-offset full" name="email" defaultValue={(user) ? user.email : null} placeholder="youlooknicetoday@email.com" required onChange={this.onChange.bind(this)} />
-                        <span className="Form-charm"></span>
+                        <FormLabel title={ managed ? "Sign in with Google Email address" : "Email address"} fieldName="email" formError={formError} ></FormLabel>
+                        <input
+                            ref="email"
+                            className={
+                              cx("Form-offset full", {
+                                "Form-input" : !managed,
+                                "text-grey-2 h1 borderless mt1": managed
+                              })
+                            }
+                            name="email"
+                            defaultValue={(user) ? user.email : null}
+                            placeholder="youlooknicetoday@email.com"
+                            required
+                            onChange={this.onChange.bind(this)}
+                            disabled={managed}
+                        />
+                        { !managed && <span className="Form-charm"></span>}
                     </FormField>
 
                     <div className="Form-actions">
diff --git a/frontend/src/metabase/user/components/UserSettings.jsx b/frontend/src/metabase/user/components/UserSettings.jsx
index df612835f1776ca09bb296a6d1b240fe4b82159d..9237edcaa30d2e6bc1f84845bee8617583fd212d 100644
--- a/frontend/src/metabase/user/components/UserSettings.jsx
+++ b/frontend/src/metabase/user/components/UserSettings.jsx
@@ -28,6 +28,7 @@ export default class UserSettings extends Component {
 
     render() {
         let { tab } = this.props;
+        const nonSSOManagedAccount = !this.props.user.google_auth
 
         let allClasses = "Grid-cell md-no-flex md-mt1 text-brand-hover bordered border-brand-hover rounded p1 md-p3 block cursor-pointer text-centered md-text-left",
             tabClasses = {};
@@ -45,17 +46,19 @@ export default class UserSettings extends Component {
                 </div>
                 <div className="mt2 md-mt4 wrapper wrapper--trim">
                     <div className="Grid Grid--gutters Grid--full md-Grid--normal md-flex-reverse">
-                        <div className="Grid-cell Grid Grid--fit md-flex-column md-Cell--1of3">
-                            <a className={cx(tabClasses['details'])}
+                        { nonSSOManagedAccount && (
+                            <div className="Grid-cell Grid Grid--fit md-flex-column md-Cell--1of3">
+                              <a className={cx(tabClasses['details'])}
                                 onClick={this.onSetTab.bind(this, 'details')}>
                                 User Details
-                            </a>
+                              </a>
 
-                            <a className={cx(tabClasses['password'])}
+                              <a className={cx(tabClasses['password'])}
                                 onClick={this.onSetTab.bind(this, 'password')}>
                                 Password
-                            </a>
-                        </div>
+                              </a>
+                            </div>
+                        )}
                         <div className="Grid-cell">
                             { tab === 'details' ?
                                 <UpdateUserDetails submitFn={this.onUpdateDetails.bind(this)} {...this.props} />
diff --git a/project.clj b/project.clj
index 8835cce5328e93882f94160f71efc2a9294ff40e..dbdc3a48e6a51e04ec7234336b999bbece7256a5 100644
--- a/project.clj
+++ b/project.clj
@@ -146,4 +146,4 @@
                               ;; Exclude everything except for reset-password specific code in the created jar
                               :jar-exclusions [#"^(?!metabase/reset_password).*$"]
                               :target-path "reset-password-artifacts/%s"} ; different than ./target because otherwise lein uberjar will delete our artifacts and vice versa
-             :h2-shell {:main org.h2.tools.Shell}})
+             :h2-shell {:main org.h2.tools.Shell}}) ; get the H2 shell with 'lein h2'
diff --git a/resources/frontend_client/app/components/icons/logo.html b/resources/frontend_client/app/components/icons/logo.html
deleted file mode 100644
index 88ab5c32ae1d2eefc16090fc041ebe39cf4d64cc..0000000000000000000000000000000000000000
--- a/resources/frontend_client/app/components/icons/logo.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<svg class="Icon" viewBox="0 0 66 85" ng-attr-width="{{width}}" ng-attr-height="{{height}}" fill="currentcolor">
-    <path d="M46.8253288,70.4935014 C49.5764899,70.4935014 51.8067467,68.1774705 51.8067467,65.3205017 C51.8067467,62.4635329 49.5764899,60.147502 46.8253288,60.147502 C44.0741676,60.147502 41.8439108,62.4635329 41.8439108,65.3205017 C41.8439108,68.1774705 44.0741676,70.4935014 46.8253288,70.4935014 Z M32.8773585,84.9779005 C35.6285197,84.9779005 37.8587764,82.6618697 37.8587764,79.8049008 C37.8587764,76.947932 35.6285197,74.6319011 32.8773585,74.6319011 C30.1261973,74.6319011 27.8959405,76.947932 27.8959405,79.8049008 C27.8959405,82.6618697 30.1261973,84.9779005 32.8773585,84.9779005 Z M32.8773585,70.4935014 C35.6285197,70.4935014 37.8587764,68.1774705 37.8587764,65.3205017 C37.8587764,62.4635329 35.6285197,60.147502 32.8773585,60.147502 C30.1261973,60.147502 27.8959405,62.4635329 27.8959405,65.3205017 C27.8959405,68.1774705 30.1261973,70.4935014 32.8773585,70.4935014 Z M18.9293882,70.4935014 C21.6805494,70.4935014 23.9108062,68.1774705 23.9108062,65.3205017 C23.9108062,62.4635329 21.6805494,60.147502 18.9293882,60.147502 C16.1782271,60.147502 13.9479703,62.4635329 13.9479703,65.3205017 C13.9479703,68.1774705 16.1782271,70.4935014 18.9293882,70.4935014 Z M46.8253288,56.0091023 C49.5764899,56.0091023 51.8067467,53.6930714 51.8067467,50.8361026 C51.8067467,47.9791337 49.5764899,45.6631029 46.8253288,45.6631029 C44.0741676,45.6631029 41.8439108,47.9791337 41.8439108,50.8361026 C41.8439108,53.6930714 44.0741676,56.0091023 46.8253288,56.0091023 Z M18.9293882,56.0091023 C21.6805494,56.0091023 23.9108062,53.6930714 23.9108062,50.8361026 C23.9108062,47.9791337 21.6805494,45.6631029 18.9293882,45.6631029 C16.1782271,45.6631029 13.9479703,47.9791337 13.9479703,50.8361026 C13.9479703,53.6930714 16.1782271,56.0091023 18.9293882,56.0091023 Z M46.8253288,26.8995984 C49.5764899,26.8995984 51.8067467,24.5835675 51.8067467,21.7265987 C51.8067467,18.8696299 49.5764899,16.553599 46.8253288,16.553599 C44.0741676,16.553599 41.8439108,18.8696299 41.8439108,21.7265987 C41.8439108,24.5835675 44.0741676,26.8995984 46.8253288,26.8995984 Z M32.8773585,41.5247031 C35.6285197,41.5247031 37.8587764,39.2086723 37.8587764,36.3517034 C37.8587764,33.4947346 35.6285197,31.1787037 32.8773585,31.1787037 C30.1261973,31.1787037 27.8959405,33.4947346 27.8959405,36.3517034 C27.8959405,39.2086723 30.1261973,41.5247031 32.8773585,41.5247031 Z M32.8773585,10.3459994 C35.6285197,10.3459994 37.8587764,8.02996853 37.8587764,5.17299969 C37.8587764,2.31603085 35.6285197,0 32.8773585,0 C30.1261973,0 27.8959405,2.31603085 27.8959405,5.17299969 C27.8959405,8.02996853 30.1261973,10.3459994 32.8773585,10.3459994 Z M32.8773585,26.8995984 C35.6285197,26.8995984 37.8587764,24.5835675 37.8587764,21.7265987 C37.8587764,18.8696299 35.6285197,16.553599 32.8773585,16.553599 C30.1261973,16.553599 27.8959405,18.8696299 27.8959405,21.7265987 C27.8959405,24.5835675 30.1261973,26.8995984 32.8773585,26.8995984 Z M18.9293882,26.8995984 C21.6805494,26.8995984 23.9108062,24.5835675 23.9108062,21.7265987 C23.9108062,18.8696299 21.6805494,16.553599 18.9293882,16.553599 C16.1782271,16.553599 13.9479703,18.8696299 13.9479703,21.7265987 C13.9479703,24.5835675 16.1782271,26.8995984 18.9293882,26.8995984 Z" opacity="0.2"></path>
-    <path d="M60.773299,70.4935014 C63.5244602,70.4935014 65.754717,68.1774705 65.754717,65.3205017 C65.754717,62.4635329 63.5244602,60.147502 60.773299,60.147502 C58.0221379,60.147502 55.7918811,62.4635329 55.7918811,65.3205017 C55.7918811,68.1774705 58.0221379,70.4935014 60.773299,70.4935014 Z M4.98141795,70.3527958 C7.73257912,70.3527958 9.96283591,68.0367649 9.96283591,65.1797961 C9.96283591,62.3228273 7.73257912,60.0067964 4.98141795,60.0067964 C2.23025679,60.0067964 0,62.3228273 0,65.1797961 C0,68.0367649 2.23025679,70.3527958 4.98141795,70.3527958 Z M60.773299,56.0091023 C63.5244602,56.0091023 65.754717,53.6930714 65.754717,50.8361026 C65.754717,47.9791337 63.5244602,45.6631029 60.773299,45.6631029 C58.0221379,45.6631029 55.7918811,47.9791337 55.7918811,50.8361026 C55.7918811,53.6930714 58.0221379,56.0091023 60.773299,56.0091023 Z M32.8773585,56.0091023 C35.6285197,56.0091023 37.8587764,53.6930714 37.8587764,50.8361026 C37.8587764,47.9791337 35.6285197,45.6631029 32.8773585,45.6631029 C30.1261973,45.6631029 27.8959405,47.9791337 27.8959405,50.8361026 C27.8959405,53.6930714 30.1261973,56.0091023 32.8773585,56.0091023 Z M4.98141795,55.8683967 C7.73257912,55.8683967 9.96283591,53.5523658 9.96283591,50.695397 C9.96283591,47.8384281 7.73257912,45.5223973 4.98141795,45.5223973 C2.23025679,45.5223973 0,47.8384281 0,50.695397 C0,53.5523658 2.23025679,55.8683967 4.98141795,55.8683967 Z M60.773299,41.5247031 C63.5244602,41.5247031 65.754717,39.2086723 65.754717,36.3517034 C65.754717,33.4947346 63.5244602,31.1787037 60.773299,31.1787037 C58.0221379,31.1787037 55.7918811,33.4947346 55.7918811,36.3517034 C55.7918811,39.2086723 58.0221379,41.5247031 60.773299,41.5247031 Z M46.8253288,41.5247031 C49.5764899,41.5247031 51.8067467,39.2086723 51.8067467,36.3517034 C51.8067467,33.4947346 49.5764899,31.1787037 46.8253288,31.1787037 C44.0741676,31.1787037 41.8439108,33.4947346 41.8439108,36.3517034 C41.8439108,39.2086723 44.0741676,41.5247031 46.8253288,41.5247031 Z M60.773299,26.8995984 C63.5244602,26.8995984 65.754717,24.5835675 65.754717,21.7265987 C65.754717,18.8696299 63.5244602,16.553599 60.773299,16.553599 C58.0221379,16.553599 55.7918811,18.8696299 55.7918811,21.7265987 C55.7918811,24.5835675 58.0221379,26.8995984 60.773299,26.8995984 Z M18.9293882,41.5247031 C21.6805494,41.5247031 23.9108062,39.2086723 23.9108062,36.3517034 C23.9108062,33.4947346 21.6805494,31.1787037 18.9293882,31.1787037 C16.1782271,31.1787037 13.9479703,33.4947346 13.9479703,36.3517034 C13.9479703,39.2086723 16.1782271,41.5247031 18.9293882,41.5247031 Z M4.98141795,41.3839975 C7.73257912,41.3839975 9.96283591,39.0679667 9.96283591,36.2109978 C9.96283591,33.354029 7.73257912,31.0379981 4.98141795,31.0379981 C2.23025679,31.0379981 0,33.354029 0,36.2109978 C0,39.0679667 2.23025679,41.3839975 4.98141795,41.3839975 Z M4.98141795,26.8995984 C7.73257912,26.8995984 9.96283591,24.5835675 9.96283591,21.7265987 C9.96283591,18.8696299 7.73257912,16.553599 4.98141795,16.553599 C2.23025679,16.553599 0,18.8696299 0,21.7265987 C0,24.5835675 2.23025679,26.8995984 4.98141795,26.8995984 Z"></path>
-</svg>
diff --git a/resources/frontend_client/index_template.html b/resources/frontend_client/index_template.html
index 2a82c4a6362f26a5d244fdbf8dc3ae712c6b70ad..32b3cc416429a4cc52474df6ca3c6e21ee684504 100644
--- a/resources/frontend_client/index_template.html
+++ b/resources/frontend_client/index_template.html
@@ -6,10 +6,11 @@
         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui" />
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+
         <title>Metabase</title>
 
-        <script>
-            window.MetabaseBootstrap = {{{bootstrap_json}}};
+        <script type="text/javascript">
+         window.MetabaseBootstrap = {{{bootstrap_json}}};
         </script>
     </head>
 
@@ -19,6 +20,7 @@
     </body>
 
     <script type="text/javascript">
+     // Load scripts asyncronously after the page has finished loading
      (function () {
          function loadScript(src, onload) {
              var script = document.createElement('script');
@@ -32,6 +34,11 @@
              WebFont.load({ google: { families: ["Lato:n3,n4,n7"] } });
          });
          loadScript('https://maps.googleapis.com/maps/api/js');
+
+         var googleAuthClientID = window.MetabaseBootstrap.google_auth_client_id;
+         if (googleAuthClientID) {
+             loadScript('https://apis.google.com/js/api:client.js');
+         }
      })();
     </script>
 
diff --git a/resources/migrations/039_add_user_google_auth_column.yaml b/resources/migrations/039_add_user_google_auth_column.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..159cb9acdeb1f5cbe577efe8c0da172585e7daf2
--- /dev/null
+++ b/resources/migrations/039_add_user_google_auth_column.yaml
@@ -0,0 +1,14 @@
+databaseChangeLog:
+  - changeSet:
+      id: 39
+      author: camsaul
+      changes:
+        - addColumn:
+            tableName: core_user
+            columns:
+              - column:
+                  name: google_auth
+                  type: boolean
+                  defaultValueBoolean: false
+                  constraints:
+                    nullable: false
diff --git a/resources/migrations/liquibase.json b/resources/migrations/liquibase.json
index c737773a3ddd37f606bebb8e0a0473e212c8d5d9..87ab77498c90b321879674ea482a131a78b8bdc0 100644
--- a/resources/migrations/liquibase.json
+++ b/resources/migrations/liquibase.json
@@ -35,6 +35,7 @@
       {"include": {"file": "migrations/034_add_pulse_channel_enabled_field.yaml"}},
       {"include": {"file": "migrations/035_modify_setting_value_length.yaml"}},
       {"include": {"file": "migrations/036_add_dashboard_filters_columns.yaml"}},
-      {"include": {"file": "migrations/037_add_query_hash_and_indexes.yaml"}}
+      {"include": {"file": "migrations/037_add_query_hash_and_indexes.yaml"}},
+      {"include": {"file": "migrations/039_add_user_google_auth_column.yaml"}}
   ]
 }
diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index 98ec89dd234cdeb058bd47ae2c4a4a33f3948328..1ef41cca3a238d87fb5eacc9b0c09e7cb36e9b45 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -2,6 +2,8 @@
   "/api/session endpoints"
   (:require [clojure.tools.logging :as log]
             [cemerick.friend.credentials :as creds]
+            [cheshire.core :as json]
+            [clj-http.client :as http]
             [compojure.core :refer [defroutes GET POST DELETE]]
             [throttle.core :as throttle]
             [metabase.api.common :refer :all]
@@ -10,18 +12,21 @@
             [metabase.events :as events]
             (metabase.models [user :refer [User set-user-password! set-user-password-reset-token!]]
                              [session :refer [Session]]
-                             [setting :as setting])
+                             [setting :refer [defsetting], :as setting])
             [metabase.util :as u]
             [metabase.util.password :as pass]))
 
 
-(defn- create-session
-  "Generate a new `Session` for a given `User`.  Returns the newly generated session id value."
-  [user-id]
+(defn- create-session!
+  "Generate a new `Session` for a given `User`. Returns the newly generated session ID."
+  [user]
+  {:pre  [(map? user) (integer? (:id user)) (contains? user :last_login)]
+   :post [(string? %)]}
   (u/prog1 (str (java.util.UUID/randomUUID))
     (db/insert! Session
       :id      <>
-      :user_id user-id)))
+      :user_id (:id user))
+    (events/publish-event :user-login {:user_id (:id user), :session_id <>, :first_login (not (boolean (:last_login user)))})))
 
 
 ;;; ## API Endpoints
@@ -43,9 +48,7 @@
                    (pass/verify-password password (:password_salt user) (:password user)))
       (throw (ex-info "Password did not match stored password." {:status-code 400
                                                                  :errors      {:password "did not match stored password"}})))
-    (let [session-id (create-session (:id user))]
-      (events/publish-event :user-login {:user_id (:id user), :session_id session-id, :first_login (not (boolean (:last_login user)))})
-      {:id session-id})))
+    {:id (create-session! user)}))
 
 
 (defendpoint DELETE "/"
@@ -73,10 +76,10 @@
   (throttle/check (forgot-password-throttlers :ip-address) remote-address)
   (throttle/check (forgot-password-throttlers :email)      email)
   ;; Don't leak whether the account doesn't exist, just pretend everything is ok
-  (when-let [user-id (db/select-one-id User, :email email)]
+  (when-let [{user-id :id, google-auth? :google_auth} (db/select-one ['User :id :google_auth] :email email)]
     (let [reset-token        (set-user-password-reset-token! user-id)
-          password-reset-url (str (@(ns-resolve 'metabase.core 'site-url) request) "/auth/reset_password/" reset-token)]
-      (email/send-password-reset-email email server-name password-reset-url)
+          password-reset-url (str ((resolve 'metabase.core/site-url) request) "/auth/reset_password/" reset-token)]
+      (email/send-password-reset-email email google-auth? server-name password-reset-url)
       (log/info password-reset-url))))
 
 
@@ -106,10 +109,8 @@
   (or (when-let [{user-id :id, :as user} (valid-reset-token->user token)]
         (set-user-password! user-id password)
         ;; after a successful password update go ahead and offer the client a new session that they can use
-        (let [session-id (create-session user-id)]
-          (events/publish-event :user-login {:user_id user-id, :session_id session-id, :first_login (not (boolean (:last_login user)))})
-          {:success    true
-           :session_id session-id}))
+        {:success    true
+         :session_id (create-session! user)})
       (throw (invalid-param-exception :password "Invalid reset token"))))
 
 
@@ -125,4 +126,67 @@
   []
   (setting/public-settings))
 
+
+;;; ------------------------------------------------------------ GOOGLE AUTH ------------------------------------------------------------
+
+(defsetting google-auth-client-id
+  "Client ID for Google Auth SSO.")
+
+(defsetting google-auth-auto-create-accounts-domain
+  "When set, allow users to sign up on their own if their Google account email address is from this domain.")
+
+(defn- google-auth-token-info [^String token]
+  (let [{:keys [status body]} (http/post (str "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" token))]
+    (when-not (= status 200)
+      (throw (ex-info "Invalid Google Auth token." {:status-code 400})))
+    (u/prog1 (json/parse-string body keyword)
+      (when-not (= (:email_verified <>) "true")
+        (throw (ex-info "Email is not verified." {:status-code 400}))))))
+
+;; TODO - are these general enough to move to `metabase.util`?
+(defn- email->domain   "ABC" ^String
+  [email]
+  (last (re-find #"^.*@(.*$)" email)))
+
+(defn- email-in-domain? ^Boolean [email domain]
+  {:pre [(u/is-email? email)]}
+  (= (email->domain email) domain))
+
+(defn- autocreate-user-allowed-for-email? [email]
+  (when-let [domain (google-auth-auto-create-accounts-domain)]
+    (email-in-domain? email domain)))
+
+(defn- check-autocreate-user-allowed-for-email [email]
+  (when-not (autocreate-user-allowed-for-email? email)
+    ;; Use some wacky status code (428 - Precondition Required) so we will know when to so the error screen specific to this situation
+    (throw (ex-info "You'll need an administrator to create a Metabase account before you can use Google to log in."
+                    {:status-code 428}))))
+
+(defn- google-auth-create-new-user! [first-name last-name email]
+  (check-autocreate-user-allowed-for-email email)
+  (db/insert! User
+    :first_name  first-name
+    :last_name   last-name
+    :email       email
+    :google_auth true
+    ;; just give the user a random password; they can go reset it if they ever change their mind and want to log in without Google Auth;
+    ;; this lets us keep the NOT NULL constraints on password / salt without having to make things hairy and only enforce those for non-Google Auth users
+    :password    (str (java.util.UUID/randomUUID))))
+
+(defn- google-auth-fetch-or-create-user! [first-name last-name email]
+  (if-let [user (or (db/select-one [User :id :last_login] :email email)
+                    (google-auth-create-new-user! first-name last-name email))]
+    {:id (create-session! user)}))
+
+(defendpoint POST "/google_auth"
+  "Login with Google Auth."
+  [:as {{:keys [token]} :body, remote-address :remote-addr}]
+  {token [Required NonEmptyString]}
+  (throttle/check (login-throttlers :ip-address) remote-address) ; TODO - Should we throttle this? It might keep us from being slammed with calls that have to make outbound HTTP requests, etc.
+  ;; Verify the token is valid with Google
+  (let [{:keys [given_name family_name email]} (google-auth-token-info token)]
+    (log/info "Successfully authenicated Google Auth token for:" given_name family_name)
+    (google-auth-fetch-or-create-user! given_name family_name email)))
+
+
 (define-routes)
diff --git a/src/metabase/api/setting.clj b/src/metabase/api/setting.clj
index 94e32bda17f1ee4b7b50c5cb4f28a0c64863dee6..5f451b7918c49d6e695e2ed091b94b02037c8274 100644
--- a/src/metabase/api/setting.clj
+++ b/src/metabase/api/setting.clj
@@ -26,11 +26,12 @@
   (setting/get (keyword key)))
 
 (defendpoint PUT "/:key"
-  "Create/update a `Setting`. You must be a superuser to do this."
+  "Create/update a `Setting`. You must be a superuser to do this.
+   This endpoint can also be used to delete Settings by passing `nil` for `:value`."
   [key :as {{:keys [value]} :body}]
-  {key Required, value Required}
+  {key Required}
   (check-superuser)
-  (setting/set (keyword key) value))
+  (setting/set* (keyword key) value))
 
 (defendpoint DELETE "/:key"
   "Delete a `Setting`. You must be a superuser to do this."
diff --git a/src/metabase/api/user.clj b/src/metabase/api/user.clj
index 0f3fb914e424119bb57d35987f2cb78fb78260cf..6163b080042f3f64908d9cb0d51d0975be4f4365 100644
--- a/src/metabase/api/user.clj
+++ b/src/metabase/api/user.clj
@@ -14,9 +14,9 @@
                  (:is_superuser @*current-user*))))
 
 (defendpoint GET "/"
-  "Fetch a list of all active `Users`."
+  "Fetch a list of all active `Users` for the admin People page."
   []
-  (db/select User, :is_active true))
+  (db/select [User :id :first_name :last_name :email :is_superuser :google_auth :last_login], :is_active true))
 
 
 (defendpoint POST "/"
diff --git a/src/metabase/db.clj b/src/metabase/db.clj
index 2aab2566eac14449defe692916c948cc3efb59c4..6ef00c23332d1a681e95b49efbdf0b7d92f1499b 100644
--- a/src/metabase/db.clj
+++ b/src/metabase/db.clj
@@ -21,7 +21,6 @@
            liquibase.Liquibase
            (liquibase.database DatabaseFactory Database)
            liquibase.database.jvm.JdbcConnection
-           liquibase.exception.DatabaseException
            liquibase.resource.ClassLoaderResourceAccessor))
 
 ;; ## DB FILE, JDBC/KORMA DEFINITONS
@@ -112,22 +111,21 @@
    *  `:release-locks` - Manually release migration locks left by an earlier failed migration.
                          (This shouldn't be necessary now that we run migrations inside a transaction,
                          but is available just in case)."
-  [db-details direction]
-  (try
-    (jdbc/with-db-transaction [conn (jdbc-details db-details)]
-      (let [^Database database (-> (DatabaseFactory/getInstance)
-                                   (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn))))
-            ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)]
-        (case direction
-          :up            (.update liquibase "")
-          :down          (.rollback liquibase 10000 "")
-          :down-one      (.rollback liquibase 1 "")
-          :print         (let [writer (StringWriter.)]
-                           (.update liquibase "" writer)
-                           (.toString writer))
-          :release-locks (.forceReleaseLocks liquibase))))
-    (catch Throwable e
-      (throw (DatabaseException. e)))))
+  ([direction]
+   (migrate @db-connection-details direction))
+  ([db-details direction]
+   (jdbc/with-db-transaction [conn (jdbc-details db-details)]
+     (let [^Database database (-> (DatabaseFactory/getInstance)
+                                  (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn))))
+           ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)]
+       (case direction
+         :up            (.update liquibase "")
+         :down          (.rollback liquibase 10000 "")
+         :down-one      (.rollback liquibase 1 "")
+         :print         (let [writer (StringWriter.)]
+                          (.update liquibase "" writer)
+                          (.toString writer))
+         :release-locks (.forceReleaseLocks liquibase))))))
 
 
 ;;; +------------------------------------------------------------------------------------------------------------------------+
diff --git a/src/metabase/email/messages.clj b/src/metabase/email/messages.clj
index 1733e0f9cf5cfa8af9f6aa8652dd9bf33f19376f..86ed67367fddda16e6247c13e3251e749ba065fa 100644
--- a/src/metabase/email/messages.clj
+++ b/src/metabase/email/messages.clj
@@ -2,6 +2,7 @@
   "Convenience functions for sending templated email messages.  Each function here should represent a single email.
    NOTE: we want to keep this about email formatting, so don't put heavy logic here RE: building data for emails."
   (:require [hiccup.core :refer [html]]
+            [medley.core :as m]
             [stencil.core :as stencil]
             [metabase.email :as email]
             [metabase.models.setting :as setting]
@@ -39,16 +40,18 @@
 
 (defn send-password-reset-email
   "Format and Send an email informing the user how to reset their password."
-  [email hostname password-reset-url]
+  [email google-auth? hostname password-reset-url]
   {:pre [(string? email)
+         (m/boolean? google-auth?)
          (u/is-email? email)
          (string? hostname)
          (string? password-reset-url)]}
   (let [message-body (stencil/render-file "metabase/email/password_reset"
-                                          {:emailType "password_reset"
-                                           :hostname hostname
+                                          {:emailType        "password_reset"
+                                           :hostname         hostname
+                                           :sso              google-auth?
                                            :passwordResetUrl password-reset-url
-                                           :logoHeader true})]
+                                           :logoHeader       true})]
     (email/send-message
      :subject      "[Metabase] Password Reset Request"
      :recipients   [email]
diff --git a/src/metabase/email/password_reset.mustache b/src/metabase/email/password_reset.mustache
index 707272452057409a05eb096336648fb3e06f6d80..87158ed55bd84f7904da3b03a41aa1e96d1ea9e4 100644
--- a/src/metabase/email/password_reset.mustache
+++ b/src/metabase/email/password_reset.mustache
@@ -2,10 +2,15 @@
   <div style="padding: 2em 4em; border: 1px solid #dddddd; border-radius: 2px; background-color: white; box-shadow: 0 1px 2px rgba(0, 0, 0, .08);">
     <p>
       You're receiving this e-mail because you or someone else has requested a password for your user account at {{hostname}}.
-      It can be safely ignored if you did not request a password reset. Click the link below to reset your password.
+      It can be safely ignored if you did not request a password reset.
     </p>
-    <p>
+    {{#sso}}
+      <p>You're using Google to log in to Metabase, so you don't have a password. You can log in to Metabase by clicking "Sign in with Google"</p>
+      <a href="{{hostname}}">Go to Metabase</a>
+    {{/sso}}
+    {{^sso}}
+      <p>Click the link below to reset your password.</p>
       <a href="{{passwordResetUrl}}">{{passwordResetUrl}}</a>
-    </p>
+    {{/sso}}
   </div>
 {{> metabase/email/footer }}
diff --git a/src/metabase/middleware.clj b/src/metabase/middleware.clj
index 64ec55eae9d30e42aa06c0b02781ba3b52fc5d99..73db9fd828436e9f6dc814c01ff0becb157f760e 100644
--- a/src/metabase/middleware.clj
+++ b/src/metabase/middleware.clj
@@ -87,7 +87,7 @@
   (fn [request]
     (if-let [current-user-id (:metabase-user-id request)]
       (binding [*current-user-id* current-user-id
-                *current-user*    (delay (db/select-one (vec (concat [User :is_active :is_staff]
+                *current-user*    (delay (db/select-one (vec (concat [User :is_active :is_staff :google_auth]
                                                                      (models/default-fields User)))
                                            :id current-user-id))]
         (handler request))
@@ -141,6 +141,7 @@
                                                                     "'unsafe-eval'"
                                                                     "'self'"
                                                                     "https://maps.google.com"
+                                                                    "https://apis.google.com"
                                                                     "https://www.google-analytics.com" ; Safari requires the protocol
                                                                     "https://*.googleapis.com"
                                                                     "*.gstatic.com"
@@ -148,6 +149,7 @@
                                                                     "*.intercom.io"
                                                                     (when config/is-dev?
                                                                       "localhost:8080")]
+                                                      :frame-src   ["https://accounts.google.com"] ; TODO - double check that we actually need this for Google Auth
                                                       :style-src   ["'unsafe-inline'"
                                                                     "'self'"
                                                                     "fonts.googleapis.com"]
diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj
index c5c3691c87e419c4cb592067bb935addc473ca1a..f7148629016ce02a03148397debd35562d1ff74e 100644
--- a/src/metabase/models/setting.clj
+++ b/src/metabase/models/setting.clj
@@ -82,7 +82,7 @@
   (env/env (keyword (str "mb-" (name setting-key)))))
 
 (defn get*
-  "Get the value of a `Setting`. Unlike `get`, this also includes values from env vars."
+  "Get the value of a `Setting`. Unlike `get`, this also includes values from env vars. Like `get`, this does not return default values."
   [setting-key]
   (or (get setting-key)
       (get-from-env-var setting-key)))
@@ -90,6 +90,16 @@
 
 ;;; ### SET / DELETE
 
+;; TODO - this should be renamed `delete!`
+(defn delete
+  "Clear the value of a `Setting`."
+  [k]
+  {:pre [(keyword? k)]}
+  (restore-cache-if-needed)
+  (swap! cached-setting->value dissoc k)
+  (db/delete! Setting, :key (name k))
+  {:status 204, :body nil})
+
 ;; TODO - rename this `set!`
 (defn set
   "Set the value of a `Setting`.
@@ -111,16 +121,6 @@
   (swap! cached-setting->value assoc k v)
   v)
 
-;; TODO - this should be renamed `delete!`
-(defn delete
-  "Clear the value of a `Setting`."
-  [k]
-  {:pre [(keyword? k)]}
-  (restore-cache-if-needed)
-  (swap! cached-setting->value dissoc k)
-  (db/delete! Setting, :key (name k))
-  {:status 204, :body nil})
-
 ;; TODO - rename this `set-all!`
 (defn set-all
   "Set the value of several `Settings` at once.
@@ -134,7 +134,7 @@
       (delete k)))
   (events/publish-event :settings-update settings))
 
-;; TODO - Rename to `set!*`
+;; TODO - Rename to `set-or-delete!`
 (defn set*
   "Set the value of a `Setting`, deleting it if VALUE is `nil` or an empty string."
   [setting-key value]
@@ -250,7 +250,8 @@
    :admin_email           (get :admin-email)
    :report_timezone       (get :report-timezone)
    :timezone_short        (short-timezone-name (get :report-timezone))
-   :has_sample_dataset    (db/exists? 'Database, :is_sample true)})
+   :has_sample_dataset    (db/exists? 'Database, :is_sample true)
+   :google_auth_client_id (get :google-auth-client-id)})
 
 
 ;;; # IMPLEMENTATION
diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj
index afe3549fc12c8e8c07de2c7d216e0b2d0e77e1f4..783a8dfd3af9e11386e0ab4877c7dfa1491a1fde 100644
--- a/src/metabase/models/user.clj
+++ b/src/metabase/models/user.clj
@@ -42,14 +42,17 @@
     (or first_name last_name) (assoc :common_name (str first_name " " last_name))))
 
 (defn- pre-cascade-delete [{:keys [id]}]
-  (db/cascade-delete! 'Session :user_id id)
-  (db/cascade-delete! 'Dashboard :creator_id id)
-  (db/cascade-delete! 'Card :creator_id id)
-  (db/cascade-delete! 'Pulse :creator_id id)
-  (db/cascade-delete! 'Activity :user_id id)
-  (db/cascade-delete! 'ViewLog :user_id id)
-  (db/cascade-delete! 'Segment :creator_id id)
-  (db/cascade-delete! 'Metric :creator_id id))
+  (doseq [[model k] [['Session        :user_id]
+                     ['Dashboard      :creator_id]
+                     ['Card           :creator_id]
+                     ['Pulse          :creator_id]
+                     ['Activity       :user_id]
+                     ['ViewLog        :user_id]
+                     ['Segment        :creator_id]
+                     ['Metric         :creator_id]
+                     ['Revision       :user_id]
+                     ['QueryExecution :executor_id]]]
+    (db/cascade-delete! model k id)))
 
 (u/strict-extend (class User)
   i/IEntity
diff --git a/src/metabase/routes.clj b/src/metabase/routes.clj
index 856660a268c9d5a36eba17ff7108b3210cb72c56..449fbb5aea4b0efdc0c4ab57b7d4d0026f37db72 100644
--- a/src/metabase/routes.clj
+++ b/src/metabase/routes.clj
@@ -9,7 +9,7 @@
             [metabase.models.setting :as setting]))
 
 (defn- index [_]
-  (-> (if (@(resolve 'metabase.core/initialized?))
+  (-> (if ((resolve 'metabase.core/initialized?))
         (stencil/render-string (slurp (or (io/resource "frontend_client/index.html")
                                           (throw (Exception. "Cannot find './resources/frontend_client/index.html'. Did you remember to build the Metabase frontend?"))))
                                {:bootstrap_json (json/generate-string (setting/public-settings))})
diff --git a/src/metabase/util.clj b/src/metabase/util.clj
index 02c14eccde0c71c5a6cda9bbc1e8f2149b309f54..4c5aaf28b062641ce7492cf39cfc751f0aa75686 100644
--- a/src/metabase/util.clj
+++ b/src/metabase/util.clj
@@ -307,24 +307,28 @@
       [default args]))
 
 
+;; TODO - rename to `email?`
 (defn is-email?
   "Is STRING a valid email address?"
-  [string]
-  (boolean (when string
+  ^Boolean [^String s]
+  (boolean (when s
              (re-matches #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
-                         (s/lower-case string)))))
+                         (s/lower-case s)))))
 
+;; TODO - rename to `url?`
 (defn is-url?
   "Is STRING a valid HTTP/HTTPS URL?"
-  [^String string]
-  (boolean (when string
-             (when-let [^java.net.URL url (try (java.net.URL. string)
-                                               (catch java.net.MalformedURLException _
+  ^Boolean [^String s]
+  (boolean (when s
+             (when-let [^java.net.URL url (try (java.net.URL. s)
+                                               (catch java.net.MalformedURLException _ ; TODO - use ignore-exceptions
                                                  nil))]
                (when (and (.getProtocol url) (.getAuthority url))
                  (and (re-matches #"^https?$" (.getProtocol url))           ; these are both automatically downcased
                       (re-matches #"^.+\..{2,}$" (.getAuthority url)))))))) ; this is the part like 'google.com'. Make sure it contains at least one period and 2+ letter TLD
 
+
+;; TODO - This should be made into a separate `sequence-of-maps?` function and a `maybe?` function
 (defn nil-or-sequence-of-maps?
   "Is VALUE either nil or sequential? such that every item is a map?"
   [v]
diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj
index 20ef46ab43a021ed04347372bb81cb95fa21d6fb..6ee4336b4c437e13282447fbc2f005e0634e2dd3 100644
--- a/test/metabase/api/session_test.clj
+++ b/test/metabase/api/session_test.clj
@@ -2,6 +2,7 @@
   "Tests for /api/session"
   (:require [cemerick.friend.credentials :as creds]
             [expectations :refer :all]
+            [metabase.api.session :refer :all]
             [metabase.db :as db]
             [metabase.http-client :refer :all]
             (metabase.models [session :refer [Session]]
@@ -9,7 +10,7 @@
             [metabase.test.data :refer :all]
             [metabase.test.data.users :refer :all]
             [metabase.util :as u]
-            [metabase.test.util :refer [random-name expect-eval-actual-first]]))
+            [metabase.test.util :refer [random-name expect-eval-actual-first resolve-private-fns with-temporary-setting-values with-temp]]))
 
 ;; ## POST /api/session
 ;; Test that we can login
@@ -173,3 +174,71 @@
 (expect
   (vec (keys (metabase.models.setting/public-settings)))
   (vec (keys ((user->client :rasta) :get 200 "session/properties"))))
+
+
+;;; ------------------------------------------------------------ TESTS FOR GOOGLE AUTH STUFF ------------------------------------------------------------
+
+(resolve-private-fns metabase.api.session email->domain email-in-domain? autocreate-user-allowed-for-email? google-auth-create-new-user! google-auth-fetch-or-create-user!)
+
+;;; tests for email->domain
+(expect "metabase.com"   (email->domain "cam@metabase.com"))
+(expect "metabase.co.uk" (email->domain "cam@metabase.co.uk"))
+(expect "metabase.com"   (email->domain "cam.saul+1@metabase.com"))
+
+;;; tests for email-in-domain?
+(expect true  (email-in-domain? "cam@metabase.com"          "metabase.com"))
+(expect false (email-in-domain? "cam.saul+1@metabase.co.uk" "metabase.com"))
+(expect true  (email-in-domain? "cam.saul+1@metabase.com"   "metabase.com"))
+
+;;; tests for autocreate-user-allowed-for-email?
+(expect
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"]
+    (autocreate-user-allowed-for-email? "cam@metabase.com")))
+
+(expect
+  false
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"]
+    (autocreate-user-allowed-for-email? "cam@expa.com")))
+
+
+;;; tests for google-auth-create-new-user!
+;; shouldn't be allowed to create a new user via Google Auth if their email doesn't match the auto-create accounts domain
+(expect
+  clojure.lang.ExceptionInfo
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com"]
+    (google-auth-create-new-user! "Rasta" "Toucan" "rasta@metabase.com")))
+
+;; should totally work if the email domains match up
+(expect
+  {:first_name "Rasta", :last_name "Toucan", :email "rasta@sf-toucannery.com"}
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com"]
+    (select-keys (u/prog1 (google-auth-create-new-user! "Rasta" "Toucan" "rasta@sf-toucannery.com")
+                   (db/cascade-delete! User :id (:id <>)))                                          ; make sure we clean up after ourselves !
+                 [:first_name :last_name :email])))
+
+
+;;; tests for google-auth-fetch-or-create-user!
+
+(defn- is-session? [session]
+  (u/ignore-exceptions
+    (java.util.UUID/fromString (:id session))
+    true))
+
+;; test that an existing user can log in with Google auth even if the auto-create accounts domain is different from their account
+;; should return a Session
+(expect
+  (with-temp User [user {:email "cam@sf-toucannery.com"}]
+    (with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"]
+      (is-session? (google-auth-fetch-or-create-user! "Cam" "Saül" "cam@sf-toucannery.com")))))
+
+;; test that a user that doesn't exist with a *different* domain than the auto-create accounts domain gets an exception
+(expect
+  clojure.lang.ExceptionInfo
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain nil]
+    (google-auth-fetch-or-create-user! "Rasta" "Can" "rasta@sf-toucannery.com")))
+
+;; test that a user that doesn't exist with the *same* domain as the auto-create accounts domain means a new user gets created
+(expect
+  (with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com"]
+    (u/prog1 (is-session? (google-auth-fetch-or-create-user! "Rasta" "Toucan" "rasta@sf-toucannery.com"))
+      (db/cascade-delete! User :email "rasta@sf-toucannery.com"))))                                       ; clean up after ourselves
diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj
index 9fe2958dd441d7d0515f626e10c0c60cb3240cbb..8f292535a8695cdd843170a90fc661d63c67bd71 100644
--- a/test/metabase/api/user_test.clj
+++ b/test/metabase/api/user_test.clj
@@ -29,34 +29,31 @@
 (expect
     #{(match-$ (fetch-user :crowberto)
         {:common_name  "Crowberto Corv"
-         :date_joined  $
          :last_name    "Corv"
          :id           $
          :is_superuser true
-         :is_qbnewb    true
          :last_login   $
          :first_name   "Crowberto"
-         :email        "crowberto@metabase.com"})
+         :email        "crowberto@metabase.com"
+         :google_auth  false})
       (match-$ (fetch-user :lucky)
         {:common_name  "Lucky Pigeon"
-         :date_joined  $
          :last_name    "Pigeon"
          :id           $
          :is_superuser false
-         :is_qbnewb    true
          :last_login   $
          :first_name   "Lucky"
-         :email        "lucky@metabase.com"})
+         :email        "lucky@metabase.com"
+         :google_auth  false})
       (match-$ (fetch-user :rasta)
         {:common_name  "Rasta Toucan"
-         :date_joined  $
          :last_name    "Toucan"
          :id           $
          :is_superuser false
-         :is_qbnewb    true
          :last_login   $
          :first_name   "Rasta"
-         :email        "rasta@metabase.com"})}
+         :email        "rasta@metabase.com"
+         :google_auth  false})}
   (do
     ;; Delete all the other random Users we've created so far
     (let [user-ids (set (map user->id [:crowberto :rasta :lucky :trashbird]))]
@@ -129,18 +126,20 @@
 
 ;; ## GET /api/user/current
 ;; Check that fetching current user will return extra fields like `is_active` and will return OrgPerms
-(expect (match-$ (fetch-user :rasta)
-          {:email        "rasta@metabase.com"
-           :first_name   "Rasta"
-           :last_name    "Toucan"
-           :common_name  "Rasta Toucan"
-           :date_joined  $
-           :last_login   $
-           :is_active    true
-           :is_staff     true
-           :is_superuser false
-           :is_qbnewb    true
-           :id           $})
+(expect
+  (match-$ (fetch-user :rasta)
+    {:email        "rasta@metabase.com"
+     :first_name   "Rasta"
+     :last_name    "Toucan"
+     :common_name  "Rasta Toucan"
+     :date_joined  $
+     :last_login   $
+     :is_active    true
+     :is_staff     true
+     :is_superuser false
+     :is_qbnewb    true
+     :google_auth  false
+     :id           $})
   ((user->client :rasta) :get 200 "user/current"))
 
 
diff --git a/test/metabase/email/messages_test.clj b/test/metabase/email/messages_test.clj
index 52fbe8bc9d7c486449e3044163ac306ffdf3e394..580419da58d47d30ee788365d0b0160aa83c6127 100644
--- a/test/metabase/email/messages_test.clj
+++ b/test/metabase/email/messages_test.clj
@@ -7,10 +7,10 @@
 ;; NOTE: we are not validating the content of the email body namely because it's got randomized elements and thus
 ;;       it would be extremely hard to have a predictable test that we can rely on
 (expect
-    [{:from "notifications@metabase.com",
-      :to ["test@test.com"],
-      :subject "You're invited to join Metabase Test's Metabase",
-      :body [{:type "text/html; charset=utf-8"}]}]
+  [{:from    "notifications@metabase.com",
+    :to      ["test@test.com"],
+    :subject "You're invited to join Metabase Test's Metabase",
+    :body    [{:type "text/html; charset=utf-8"}]}]
   (with-fake-inbox
     (send-new-user-email {:first_name "test" :email "test@test.com"}
                          {:first_name "invitor" :email "invited_by@test.com"}
@@ -20,11 +20,11 @@
 
 ;; password reset email
 (expect
-    [{:from "notifications@metabase.com",
-      :to ["test@test.com"],
-      :subject "[Metabase] Password Reset Request",
-      :body [{:type "text/html; charset=utf-8"}]}]
+  [{:from    "notifications@metabase.com",
+    :to      ["test@test.com"],
+    :subject "[Metabase] Password Reset Request",
+    :body    [{:type "text/html; charset=utf-8"}]}]
   (with-fake-inbox
-    (send-password-reset-email "test@test.com" "test.domain.com" "http://localhost/some/url")
+    (send-password-reset-email "test@test.com" (not :google-auth) "test.domain.com" "http://localhost/some/url")
     (-> (@inbox "test@test.com")
         (update-in [0 :body 0] dissoc :content))))
diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj
index b34845ecd304b01b490bf166c6a783f6e4a17749..bd61d82a718ccce2c3467d65eba5dc813003e62a 100644
--- a/test/metabase/test/util.clj
+++ b/test/metabase/test/util.clj
@@ -16,6 +16,7 @@
                              [raw-table :refer [RawTable]]
                              [revision :refer [Revision]]
                              [segment :refer [Segment]]
+                             [setting :as setting]
                              [table :refer [Table]]
                              [user :refer [User]])
             [metabase.test.data :as data]
@@ -92,7 +93,7 @@
 
 
 (defn boolean-ids-and-timestamps
-  "Useful for unit test comparisons.  Converts map keys with 'id' or '_at' to booleans."
+  "Useful for unit test comparisons. Converts map keys with 'id' or '_at' to booleans."
   [m]
   (let [f (fn [v]
             (cond
@@ -204,10 +205,10 @@
 
 (u/strict-extend (class User)
   WithTempDefaults
-  {:with-temp-defaults (fn [_] {:email      (str (random-name) "@metabase.com")
-                                :password   (random-name)
-                                :first_name (random-name)
-                                :last_name  (random-name)})})
+  {:with-temp-defaults (fn [_] {:first_name (random-name)
+                                :last_name  (random-name)
+                                :email      (str (random-name) "@metabase.com")
+                                :password   (random-name)})})
 
 
 (defn do-with-temp
@@ -276,23 +277,29 @@
            (first @~with-temp-form))   ; if dereferencing with-temp-form throws an exception then expect Exception <-> Exception will pass; we don't want that, so make sure the expected
          (second @~with-temp-form))))) ; case is nil if we encounter an exception so the two don't match and the test doesn't succeed
 
-;; ## resolve-private-fns
+
+(defn- namespace-or-symbol? [x]
+  (or (symbol? x)
+      (instance? clojure.lang.Namespace x)))
+
+(defn resolve-private-fns* [source-namespace target-namespace symbols]
+  {:pre [(namespace-or-symbol? source-namespace)
+         (namespace-or-symbol? target-namespace)
+         (every? symbol? symbols)]}
+  (require source-namespace)
+  (doseq [symb symbols
+          :let [varr (or (ns-resolve source-namespace symb)
+                         (throw (Exception. (str source-namespace "/" symb " doesn't exist!"))))]]
+    (intern target-namespace symb varr)))
 
 (defmacro resolve-private-fns
   "Have your cake and eat it too. This Macro adds private functions from another namespace to the current namespace so we can test them.
 
     (resolve-private-fns metabase.driver.generic-sql.sync
       field-avg-length field-percent-urls)"
-  {:arglists '([namespace-symb & fn-symbs])}
-  [namespc fn-name & more]
-  {:pre [(symbol? namespc)
-         (symbol? fn-name)
-         (every? symbol? more)]}
-  `(do (require '~namespc)
-       (def ~(vary-meta fn-name assoc :private true) (or (ns-resolve '~namespc '~fn-name)
-                                                         (throw (Exception. ~(str namespc "/" fn-name " doesn't exist!")))))
-       ~(when (seq more)
-          `(resolve-private-fns ~namespc ~(first more) ~@(rest more)))))
+  [namespc & symbols]
+  `(resolve-private-fns* (quote ~namespc) *ns* (quote ~symbols)))
+
 
 (defn obj->json->obj
   "Convert an object to JSON and back again. This can be done to ensure something will match its serialized + deserialized form,
@@ -314,3 +321,28 @@
                      (into {} x)
                      x))
                  coll))
+
+
+(defn do-with-temporary-setting-value
+  "Temporarily set the value of the `Setting` named by keyword SETTING-K to VALUE and execute F, then re-establish the original value.
+   This works much the same way as `binding`.
+
+   Prefer the macro `with-temporary-setting-values` over using this function directly."
+  [setting-k value f]
+  (let [original-value (setting/get setting-k)]
+    (try
+      (setting/set* setting-k value)
+      (f)
+      (finally
+        (setting/set* setting-k original-value)))))
+
+(defmacro with-temporary-setting-values
+  "Temporarily bind the values of one or more `Settings`, execute body, and re-establish the original values. This works much the same way as `binding`.
+
+     (with-temporary-setting-values [google-auth-auto-create-accounts-domain \"metabase.com\"]
+       (google-auth-auto-create-accounts-domain)) -> \"metabase.com\""
+  [[setting-k value & more] & body]
+  (let [body `(do-with-temporary-setting-value ~(keyword setting-k) ~value (fn [] ~@body))]
+    (if (seq more)
+      `(with-temporary-setting-values ~more ~body)
+      body)))