Skip to content
Snippets Groups Projects
Commit 07029edc authored by William Turner's avatar William Turner
Browse files

Adds basic working auth endpoint

parent 0d1b76f8
No related branches found
No related tags found
No related merge requests found
......@@ -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: [
......
......@@ -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'
......
databaseChangeLog:
- changeSet:
id: 50
author: wwwiiilll
changes:
- addColumn:
tableName: core_user
columns:
- column:
name: ldap_auth
type: boolean
defaultValueBoolean: false
constraints:
nullable: false
(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]
......@@ -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]
......@@ -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)
......@@ -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)))
......
......@@ -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!
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment