From 689cabb5fec43a6b82e72b73465182dbe9aa1a35 Mon Sep 17 00:00:00 2001
From: William Turner <william.turner@aero.bombardier.com>
Date: Wed, 8 Feb 2017 20:57:18 -0500
Subject: [PATCH] Adds UI integration (needs a lot more work)

---
 frontend/src/metabase/auth/auth.js            |  7 +++--
 .../src/metabase/auth/containers/LoginApp.jsx | 28 +++++++++++++------
 frontend/src/metabase/lib/settings.js         |  4 +++
 frontend/src/metabase/services.js             |  1 +
 src/metabase/api/session.clj                  | 19 ++++++-------
 src/metabase/public_settings.clj              |  1 +
 6 files changed, 40 insertions(+), 20 deletions(-)

diff --git a/frontend/src/metabase/auth/auth.js b/frontend/src/metabase/auth/auth.js
index b75b5db49a1..9cbe1536515 100644
--- a/frontend/src/metabase/auth/auth.js
+++ b/frontend/src/metabase/auth/auth.js
@@ -6,6 +6,7 @@ import { push } from "react-router-redux";
 import MetabaseCookies from "metabase/lib/cookies";
 import MetabaseUtils from "metabase/lib/utils";
 import MetabaseAnalytics from "metabase/lib/analytics";
+import MetabaseSettings from "metabase/lib/settings.js";
 
 import { clearGoogleAuthCredentials } from "metabase/lib/auth";
 
@@ -19,12 +20,14 @@ export const LOGIN = "metabase/auth/LOGIN";
 export const login = createThunkAction(LOGIN, function(credentials, redirectUrl) {
     return async function(dispatch, getState) {
 
-        if (!MetabaseUtils.validEmail(credentials.email)) {
+        if (!MetabaseSettings.ldapEnabled() && !MetabaseUtils.validEmail(credentials.email)) {
             return {'data': {'errors': {'email': "Please enter a valid formatted email address."}}};
         }
 
         try {
-            let newSession = await SessionApi.create(credentials);
+            let newSession = MetabaseSettings.ldapEnabled()
+                ? await SessionApi.createWithLdap(credentials)
+                : await SessionApi.create(credentials);
 
             // since we succeeded, lets set the session cookie
             MetabaseCookies.setSessionCookie(newSession.id);
diff --git a/frontend/src/metabase/auth/containers/LoginApp.jsx b/frontend/src/metabase/auth/containers/LoginApp.jsx
index 229febd771c..a9d4a2435ba 100644
--- a/frontend/src/metabase/auth/containers/LoginApp.jsx
+++ b/frontend/src/metabase/auth/containers/LoginApp.jsx
@@ -42,9 +42,11 @@ export default class LoginApp extends Component {
     validateForm() {
         let { credentials } = this.state;
 
-        let valid = true;
+        let valid = Settings.ldapEnabled()
+            ? !!credentials.username
+            : !!credentials.email;
 
-        if (!credentials.email || !credentials.password) {
+        if (!credentials.password) {
             valid = false;
         }
 
@@ -128,11 +130,19 @@ export default class LoginApp extends Component {
 
                             <FormMessage formError={loginError && loginError.data.message ? loginError : null} ></FormMessage>
 
-                            <FormField key="email" fieldName="email" formError={loginError}>
-                                <FormLabel title={"Email address"}  fieldName={"email"} formError={loginError} />
-                                <input className="Form-input Form-offset full py1" name="email" placeholder="youlooknicetoday@email.com" type="text" onChange={(e) => this.onChange("email", e.target.value)} autoFocus />
-                                <span className="Form-charm"></span>
-                            </FormField>
+                            { Settings.ldapEnabled() ? (
+                                <FormField key="username" fieldName="username" formError={loginError}>
+                                    <FormLabel title={"Username or Email address"}  fieldName={"username"} formError={loginError} />
+                                    <input className="Form-input Form-offset full py1" name="username" placeholder="youlooknicetoday@email.com" type="text" onChange={(e) => this.onChange("username", e.target.value)} autoFocus />
+                                    <span className="Form-charm"></span>
+                                </FormField>
+                            ) : (
+                                <FormField key="email" fieldName="email" formError={loginError}>
+                                    <FormLabel title={"Email address"}  fieldName={"email"} formError={loginError} />
+                                    <input className="Form-input Form-offset full py1" name="email" placeholder="youlooknicetoday@email.com" type="text" onChange={(e) => this.onChange("email", e.target.value)} autoFocus />
+                                    <span className="Form-charm"></span>
+                                </FormField>
+                            )}
 
                             <FormField key="password" fieldName="password" formError={loginError}>
                                 <FormLabel title={"Password"}  fieldName={"password"} formError={loginError} />
@@ -150,7 +160,9 @@ export default class LoginApp extends Component {
                                 <button className={cx("Button Grid-cell", {'Button--primary': this.state.valid})} disabled={!this.state.valid}>
                                     Sign in
                                 </button>
-                                <Link to={"/auth/forgot_password"+(this.state.credentials.email ? "?email="+this.state.credentials.email : "")} className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</Link>
+                                { (!Settings.ldapEnabled()) &&
+                                    <Link to={"/auth/forgot_password"+(this.state.credentials.email ? "?email="+this.state.credentials.email : "")} className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</Link>
+                                }
                             </div>
                         </form>
                     </div>
diff --git a/frontend/src/metabase/lib/settings.js b/frontend/src/metabase/lib/settings.js
index 5460a7b9568..4c0fdfaefbc 100644
--- a/frontend/src/metabase/lib/settings.js
+++ b/frontend/src/metabase/lib/settings.js
@@ -51,6 +51,10 @@ const MetabaseSettings = {
         return mb_settings.google_auth_client_id != null;
     },
 
+    ldapEnabled: function() {
+        return mb_settings.ldap_configured;
+    },
+
     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 7705b6c16e9..a5e37dd65a1 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -172,6 +172,7 @@ export const LabelApi = {
 export const SessionApi = {
     create:                     POST("/api/session"),
     createWithGoogleAuth:       POST("/api/session/google_auth"),
+    createWithLdap:             POST("/api/session/ldap_auth"),
     delete:                   DELETE("/api/session"),
     properties:                  GET("/api/session/properties"),
     forgot_password:            POST("/api/session/forgot_password"),
diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index 93893241a7a..a9ea0f0e0a2 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -38,6 +38,7 @@
 
 (def ^:private login-throttlers
   {:email      (throttle/make-throttler :email)
+   :username   (throttle/make-throttler :username)
    :ip-address (throttle/make-throttler :email, :attempts-threshold 50)}) ; IP Address doesn't have an actual UI field so just show error by email
 
 (defendpoint POST "/"
@@ -220,8 +221,7 @@
   "The password to bind with.")
 
 (defsetting ldap-base
-  "Search base for users."
-  :default "")
+  "Search base for users.")
 
 (defsetting ldap-user-filter
   "Filter to use for looking up a specific user, the placeholder {login} will be replaced by the user supplied value."
@@ -273,16 +273,15 @@
 
 (defendpoint POST "/ldap_auth"
   "Login with LDAP auth."
-  [:as {{:keys [email password]} :body, remote-address :remote-addr}]
-  {email    su/Email
+  [:as {{:keys [username password]} :body, remote-address :remote-addr}]
+  {username su/NonBlankString
    password su/NonBlankString}
   (throttle/check (login-throttlers :ip-address) remote-address)
-  (throttle/check (login-throttlers :email)      email)
-  (let [user (ldap-auth-user-info email password)]
-    (when (nil? user)
-      (throw (ex-info "Password did not match stored password." {:status-code 400
-                                                                 :errors      {:password "did not match stored password"}})))
-    (ldap-auth-fetch-or-create-user! (:first-name user) (:last-name user) (:email user))))
+  (throttle/check (login-throttlers :username)   username)
+  (if-let [{:keys [first-name last-name email]} (ldap-auth-user-info username password)]
+    (ldap-auth-fetch-or-create-user! first-name last-name email)
+    (throw (ex-info "Password did not match stored password." {:status-code 400
+                                                               :errors      {:password "did not match stored password"}}))))
 
 
 (define-routes)
diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj
index 4d9900614fa..6ed7b788eec 100644
--- a/src/metabase/public_settings.clj
+++ b/src/metabase/public_settings.clj
@@ -119,6 +119,7 @@
    :engines               ((resolve 'metabase.driver/available-drivers))
    :ga_code               "UA-60817802-1"
    :google_auth_client_id (setting/get :google-auth-client-id)
+   :ldap_configured       ((resolve 'metabase.api.session/ldap-configured?))
    :has_sample_dataset    (db/exists? 'Database, :is_sample true)
    :map_tile_server_url   (map-tile-server-url)
    :password_complexity   password/active-password-complexity
-- 
GitLab