Skip to content

Commit

Permalink
Adds basic working auth endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
willtrnr committed Apr 24, 2017
1 parent 0d1b76f commit 07029ed
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 4 deletions.
71 changes: 71 additions & 0 deletions frontend/src/metabase/admin/settings/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,77 @@ const SECTIONS = [
}
]
},
{
name: "LDAP",
settings: [
{
key: "ldap-host",
display_name: "LDAP Host",
placeholder: "ldap.yourdomain.org",
type: "string",
required: true,
autoFocus: true
},
{
key: "ldap-port",
display_name: "LDAP Port",
placeholder: "389",
type: "string",
required: true,
validations: [["integer", "That's not a valid port number"]]
},
{
key: "ldap-security",
display_name: "LDAP Security",
description: null,
type: "radio",
options: { none: "None", ssl: "SSL", tls: "TLS" },
defaultValue: 'none'
},
{
key: "ldap-bind-dn",
display_name: "Username or DN",
type: "string",
required: true
},
{
key: "ldap-password",
display_name: "Password",
type: "password",
required: true
},
{
key: "ldap-base",
display_name: "Search base",
type: "string",
required: true
},
{
key: "ldap-user-filter",
display_name: "User filter",
type: "string",
required: true
},
{
key: "ldap-attribute-email",
display_name: "User email attribute",
type: "string",
required: true
},
{
key: "ldap-attribute-firstname",
display_name: "User first name attribute",
type: "string",
required: true
},
{
key: "ldap-attribute-lastname",
display_name: "User last name attribute",
type: "string",
required: true
}
]
},
{
name: "Maps",
settings: [
Expand Down
3 changes: 2 additions & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
[ring/ring-json "0.4.0"] ; Ring middleware for reading/writing JSON automatically
[stencil "0.5.0"] ; Mustache templates for Clojure
[toucan "1.0.2" ; Model layer, hydration, and DB utilities
:exclusions [honeysql]]]
:exclusions [honeysql]]
[org.clojars.pntblnk/clj-ldap "0.0.12"]] ; LDAP library
:repositories [["bintray" "https://dl.bintray.com/crate/crate"]] ; Repo for Crate JDBC driver
:plugins [[lein-environ "1.1.0"] ; easy access to environment variables
[lein-ring "0.11.0" ; start the HTTP server with 'lein ring server'
Expand Down
14 changes: 14 additions & 0 deletions resources/migrations/050_add_user_ldap_auth_column.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
databaseChangeLog:
- changeSet:
id: 50
author: wwwiiilll
changes:
- addColumn:
tableName: core_user
columns:
- column:
name: ldap_auth
type: boolean
defaultValueBoolean: false
constraints:
nullable: false
95 changes: 93 additions & 2 deletions src/metabase/api/session.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
(ns metabase.api.session
"/api/session endpoints"
(:require [clojure.tools.logging :as log]
(:require [clojure.string :as str]
[clojure.tools.logging :as log]
[cemerick.friend.credentials :as creds]
[cheshire.core :as json]
[clj-http.client :as http]
[clj-ldap.client :as ldap]
[compojure.core :refer [defroutes GET POST DELETE]]
[schema.core :as s]
[throttle.core :as throttle]
Expand All @@ -13,7 +15,7 @@
[metabase.events :as events]
(metabase.models [user :refer [User], :as user]
[session :refer [Session]]
[setting :refer [defsetting]])
[setting :refer [defsetting], :as setting])
[metabase.public-settings :as public-settings]
[metabase.util :as u]
(metabase.util [password :as pass]
Expand Down Expand Up @@ -194,4 +196,93 @@
(google-auth-fetch-or-create-user! given_name family_name email)))


;;; ------------------------------------------------------------ LDAP AUTH ------------------------------------------------------------

(defsetting ldap-host
"Server hostname. If this is set, LDAP auth is considered to be enabled.")

(defsetting ldap-port
"Server port."
:default "389")

(defsetting ldap-security
"Connect over SSL (LDAPS) or TLS"
:default "none"
:setter (fn [new-value]
(when-not (nil? new-value)
(assert (contains? #{"none" "ssl" "tls"} new-value)))
(setting/set-string! :ldap-security new-value)))

(defsetting ldap-bind-dn
"The DN to bind as.")

(defsetting ldap-password
"The password to bind with.")

(defsetting ldap-base
"Search base for users."
:default "")

(defsetting ldap-user-filter
"Filter to use for looking up a specific user, the placeholder {login} will be replaced by the user supplied value."
:default "(&(objectClass=inetOrgPerson)(|(uid={login})(mail={login})))")

(defsetting ldap-attribute-email
"Attribute to use for the user's email."
:default "mail")

(defsetting ldap-attribute-firstname
"Attribute to use for the user's first name."
:default "givenName")

(defsetting ldap-attribute-lastname
"Attribute to use for the user's last name."
:default "sn")

(defn ldap-configured?
"Predicate function which returns `true` if we have an LDAP server configured, `false` otherwise."
[]
(boolean (ldap-host)))

(defn- ldap-connection []
(ldap/connect {:host (str (ldap-host) ":" (ldap-port))
:bind-dn (ldap-bind-dn)
:password (ldap-password)
:ssl? (= (ldap-security) "ssl")
:startTLS? (= (ldap-security) "tls")}))

(defn- ldap-auth-user-info [email password]
(let [fname-attr (keyword (ldap-attribute-firstname))
lname-attr (keyword (ldap-attribute-lastname))
email-attr (keyword (ldap-attribute-email))]
;; first figure out the user info if it even exists
(when-let [[result] (ldap/search (ldap-connection) (ldap-base) {:scope :sub
:filter (str/replace (ldap-user-filter) "{login}" email)
:attributes [:dn :distinguishedName fname-attr lname-attr email-attr]
:size-limit 1})]
;; then validate the password by binding with it
(when (ldap/bind? (ldap-connection) (or (:dn result) (:distinguishedName result)) password)
{:first-name (get result fname-attr)
:last-name (get result lname-attr)
:email (get result email-attr)}))))

(defn- ldap-auth-fetch-or-create-user! [first-name last-name email]
(if-let [user (or (db/select-one [User :id :last_login] :email email)
(user/create-new-ldap-auth-user! first-name last-name email))]
{:id (create-session! user)}))

(defendpoint POST "/ldap_auth"
"Login with LDAP auth."
[:as {{:keys [email password]} :body, remote-address :remote-addr}]
{email su/Email
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))))


(define-routes)
4 changes: 3 additions & 1 deletion src/metabase/api/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
:is_superuser false
;; if the user orignally logged in via Google Auth and it's no longer enabled, convert them into a regular user (see Issue #3323)
:google_auth (boolean (and (:google_auth existing-user)
(session-api/google-auth-client-id))))) ; if google-auth-client-id is set it means Google Auth is enabled
(session-api/google-auth-client-id))) ; if google-auth-client-id is set it means Google Auth is enabled
:ldap_auth (boolean (and (:ldap_auth existing-user)
(session-api/ldap-configured?)))))
;; now return the existing user whether they were originally active or not
(User (u/get-id existing-user)))

Expand Down
12 changes: 12 additions & 0 deletions src/metabase/models/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@
;; send an email to everyone including the site admin if that's set
(email/send-user-joined-admin-notification-email! <>, :google-auth? true)))

(defn create-new-ldap-auth-user!
"Convenience for creating a new user via Google Auth. This account is considered active immediately; thus all active admins will recieve an email right away."
[first-name last-name email-address]
{:pre [(string? first-name) (string? last-name) (u/is-email? email-address)]}
(u/prog1 (db/insert! User
:email email-address
:first_name first-name
:last_name last-name
:password (str (UUID/randomUUID))
:ldap_auth true)
;; send an email to everyone including the site admin if that's set
(email/send-user-joined-admin-notification-email! <>, :ldap-auth? true)))


(defn set-password!
Expand Down

0 comments on commit 07029ed

Please sign in to comment.