Functions shared by the various SSO implementations | (ns metabase-enterprise.sso.integrations.sso-utils (:require [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] [metabase.api.common :as api] [metabase.channel.email.messages :as messages] [metabase.events :as events] [metabase.events.notification :as events.notification] [metabase.public-settings :as public-settings] [metabase.sso.core :as sso] [metabase.util :as u] [metabase.util.i18n :refer [trs tru]] [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [toucan2.core :as t2]) (:import (clojure.lang ExceptionInfo) (java.net URI URISyntaxException))) |
(set! *warn-on-reflection* true) | |
(def ^:private UserAttributes
[:map {:closed true}
[:first_name [:maybe ms/NonBlankString]]
[:last_name [:maybe ms/NonBlankString]]
[:email ms/Email]
;; TODO - we should avoid hardcoding this to make it easier to add new integrations. Maybe look at something like
;; the keys of `(methods sso/sso-get)`
[:sso_source [:enum :saml :jwt]]
[:login_attributes [:maybe :map]]]) | |
(defn- maybe-throw-user-provisioning
[user-provisioning-type]
(when (not user-provisioning-type)
(throw (ex-info (trs "Sorry, but you''ll need a {0} account to view this page. Please contact your administrator."
(u/slugify (public-settings/site-name))) {})))) | |
If | (defmulti check-user-provisioning
{:arglists '([model])}
keyword) |
(defmethod check-user-provisioning :saml [_] (maybe-throw-user-provisioning (sso-settings/saml-user-provisioning-enabled?))) | |
(defmethod check-user-provisioning :ldap [_] (maybe-throw-user-provisioning (sso-settings/ldap-user-provisioning-enabled?))) | |
(defmethod check-user-provisioning :jwt [_] (maybe-throw-user-provisioning (sso-settings/jwt-user-provisioning-enabled?))) | |
This function is basically the same thing as the | (mu/defn create-new-sso-user!
[user :- UserAttributes]
(try
(u/prog1 (t2/insert-returning-instance! :model/User (merge user {:password (str (random-uuid))}))
(log/infof "New SSO user created: %s (%s)" (:common_name <>) (:email <>))
;; publish user-invited event for audit logging
;; skip sending user invited emails for sso users
(binding [events.notification/*skip-sending-notification?* true]
(events/publish-event! :event/user-invited {:object (assoc <> :sso_source (:sso_source user))}))
;; send an email to everyone including the site admin if that's set
(when (sso/send-new-sso-user-admin-email?)
(messages/send-user-joined-admin-notification-email! <>, :google-auth? true)))
(catch ExceptionInfo e
(log/error e "Error creating new SSO user")
(throw (ex-info (trs "Error creating new SSO user")
{:user user}))))) |
Update | (defn fetch-and-update-login-attributes!
[{:keys [email] :as user-from-sso}]
(when-let [{:keys [id] :as user} (t2/select-one :model/User :%lower.email (u/lower-case-en email))]
(let [user-keys (keys user-from-sso)
;; remove keys with `nil` values
user-data (into {} (filter second user-from-sso))]
(if (= (select-keys user user-keys) user-data)
user
(do
(t2/update! :model/User id user-data)
(t2/select-one :model/User :id id)))))) |
Checks that given | (defn relative-uri?
[uri]
(let [^URI uri (if (string? uri)
(try
(URI. uri)
(catch URISyntaxException _
nil))
uri)]
(or (nil? uri)
(and (nil? (.getHost uri))
(nil? (.getScheme uri)))))) |
Check if open redirect is being exploited in SSO. If so, or if the redirect-url is invalid, throw a 400. | (defn check-sso-redirect
[redirect-url]
(try
(let [redirect (some-> redirect-url (URI.))
our-host (some-> (public-settings/site-url) (URI.) (.getHost))]
(api/check-400 (or (nil? redirect-url)
(relative-uri? redirect)
(= (.getHost redirect) our-host))))
(catch Exception e
(log/error e "Invalid redirect URL")
(throw (ex-info (tru "Invalid redirect URL")
{:status-code 400
:redirect-url redirect-url}))))) |