(ns metabase.session.models.session (:require [buddy.core.codecs :as codecs] [buddy.core.nonce :as nonce] [metabase.config :as config] [metabase.db :as mdb] [metabase.driver.sql.query-processor :as sql.qp] [metabase.events :as events] [metabase.login-history.core :as login-history] [metabase.public-settings :as public-settings] [metabase.request.core :as request] [metabase.util :as u] [metabase.util.i18n :refer [tru]] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [methodical.core :as methodical] [toucan2.core :as t2])) | |
(mu/defn- random-anti-csrf-token :- [:re {:error/message "valid anti-CSRF token"} #"^[0-9a-f]{32}$"]
[]
(codecs/bytes->hex (nonce/random-bytes 16))) | |
(methodical/defmethod t2/table-name :model/Session [_model] :core_session) | |
(doto :model/Session (derive :metabase/model) (derive :hook/created-at-timestamped?)) | |
(t2/define-before-update :model/Session [_model] (throw (RuntimeException. "You cannot update a Session."))) | |
(t2/define-before-insert :model/Session
[session]
(cond-> session
(some-> (request/current-request) request/embedded?) (assoc :anti_csrf_token (random-anti-csrf-token)))) | |
(t2/define-after-insert :model/Session
[{anti-csrf-token :anti_csrf_token, :as session}]
(let [session-type (if anti-csrf-token :full-app-embed :normal)]
(assoc session :type session-type))) | |
(def ^:private CreateSessionUserInfo [:map [:id ms/PositiveInt] [:last_login :any]]) | |
Schema for a Session. | (def SessionSchema
[:and
[:map-of :keyword :any]
[:map
[:id uuid?]
[:type [:enum :normal :full-app-embed]]]]) |
Generate a new Session for a User. | (defmulti create-session!
{:arglists '([session-type user device-info])}
(fn [session-type & _]
session-type)) |
(mu/defmethod create-session! :sso :- SessionSchema
[_ user :- CreateSessionUserInfo device-info :- request/DeviceInfo]
(let [session-id (random-uuid)
session (first (t2/insert-returning-instances! :model/Session
:id (str session-id)
:user_id (u/the-id user)))]
(assert (map? session))
(let [event {:user-id (u/the-id user)}]
(events/publish-event! :event/user-login event)
(when (nil? (:last_login user))
(events/publish-event! :event/user-joined event)))
(login-history/record-login-history! session-id user device-info)
(assoc session :id session-id))) | |
(mu/defmethod create-session! :password :- SessionSchema
[session-type
user :- CreateSessionUserInfo
device-info :- request/DeviceInfo]
;; this is actually the same as `create-session!` for `:sso` but we check whether password login is enabled.
(when-not (public-settings/enable-password-login)
(throw (ex-info (str (tru "Password login is disabled for this instance.")) {:status-code 400})))
((get-method create-session! :sso) session-type user device-info)) | |
Deletes sessions from the database which are no longer valid | (defn cleanup-sessions!
[]
(let [oldest-allowed [:inline (sql.qp/add-interval-honeysql-form (mdb/db-type)
:%now
(- (config/config-int :max-session-age))
:minute)]]
(t2/delete! :model/Session :created_at [:< oldest-allowed]))) |