From e422a525766f557415e4fa97fcadb1c8d577904d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20R=C3=ADos?= Date: Wed, 31 Jan 2024 14:14:23 +0100 Subject: [PATCH] Get jwt from cookie and validation key from file --- src/metabase/stratio/auth.clj | 19 +++++-- src/metabase/stratio/config.clj | 37 +++++++++++--- src/metabase/stratio/header_user_info.clj | 62 +++++++++++++---------- src/metabase/stratio/util.clj | 7 +++ 4 files changed, 86 insertions(+), 39 deletions(-) diff --git a/src/metabase/stratio/auth.clj b/src/metabase/stratio/auth.clj index 975954571edb5..de9b7f373cd99 100644 --- a/src/metabase/stratio/auth.clj +++ b/src/metabase/stratio/auth.clj @@ -34,6 +34,13 @@ ;; a set can act as a predicate! (some whitelist groups))) +(defn- tenant-allowed? + [user-tenant] + (let [app-tenant (st.config/config-str :tenant)] + (or (not user-tenant) + (not app-tenant) + (= user-tenant app-tenant)))) + (defn- admin? [groups] (contains? (set groups) admin-group)) @@ -46,14 +53,16 @@ superuser? (conj group/admin-group-name))) (defn- allowed-user - [{:keys [user groups error]}] + [{:keys [user groups email tenant error]}] (if error {:error error} - (if (allowed? groups) + (if (and (allowed? groups) (tenant-allowed? tenant)) {:first_name user :last_name "" :is_superuser (admin? groups) - :email (if (u/email? user) user (u/lower-case-en (str user dummy-email-domain))) + :email (cond email email + (u/email? user) user + :else (u/lower-case-en (str user dummy-email-domain))) :login_attributes {:groups groups}} {:error (str "User " user " not allowed")}))) @@ -102,8 +111,8 @@ user-inserted))) (defn create-session-from-headers! - [{headers :headers, :as request}] - (let [user-info (http-headers->user-info headers) + [request] + (let [user-info (http-headers->user-info request) allowed-user (allowed-user user-info)] (log/debug "received user info " user-info) (if (:error allowed-user) diff --git a/src/metabase/stratio/config.clj b/src/metabase/stratio/config.clj index 9311b66b5f860..d6ba39bbb27e2 100644 --- a/src/metabase/stratio/config.clj +++ b/src/metabase/stratio/config.clj @@ -1,5 +1,7 @@ (ns metabase.stratio.config - (:require [clojure.string :as str] + (:require [buddy.core.keys :as keys] + [clj-http.client :as http] + [clojure.string :as str] [metabase.config :as config] [metabase.models.setting :refer [defsetting]] [metabase.stratio.util :as st.util])) @@ -11,8 +13,13 @@ :jwt-header-name "X-USER-TOKEN" :jwt-username-claim "sub" :jwt-groups-claim "groups" + :jwt-tenant-claim "tenant" + :jwt-email-claim "mail" + :jwt-public-key-location "url" :jwt-public-key-endpoint "" + :jwt-public-key-file "/etc/pki/jwt-public-key.pem" :jwt-insecure-request-pkey "false" + :jwt-cookie-name "stratio-cookie" ;; settings for authentication via headers :mb-user-header "" @@ -32,15 +39,33 @@ (defn config-kw [k] (some-> k config-str keyword)) (defn config-vector [k] (st.util/make-vector (config-str k))) +(defn- ssl-config [] + (if (config-bool :jwt-insecure-request-pkey) + {:insecure? true} + {:trust-store (config-str :mb-jetty-ssl-truststore) + :trust-store-pass (config-str :mb-jetty-ssl-truststore-password)})) + (def auto-login-authenticators #{:jwt :headers :gosec-sso}) -(def authenticator (config-kw :authenticator)) -(def should-auto-login? (contains? auto-login-authenticators authenticator)) -(def jwt? (= authenticator :jwt)) -(def gosec-sso? (= authenticator :gosec-sso)) +(def authenticator (config-kw :authenticator)) +(def jwt-public-key-location (config-kw :jwt-public-key-location)) +(def should-auto-login? (contains? auto-login-authenticators authenticator)) +(def jwt? (= authenticator :jwt)) +(def gosec-sso? (= authenticator :gosec-sso)) +(def headers? (= authenticator :headers)) +(def jwt-cookie-name (config-str :jwt-cookie-name)) +(def jwt-public-key (delay + (if (= jwt-public-key-location :file) + (-> (config-str :jwt-public-key-file) + (slurp) + (keys/str->public-key)) + (-> (config-str :jwt-public-key-endpoint) + (http/get (ssl-config)) + (:body) + (keys/str->public-key))))) ;; We need to define a setting so it can reach frontend via MetabaseSettings object (defsetting gosec-sso-enabled - "flag to tell the front end if we are behing the sso proxy so when logout redirect to proxy logout" + "flag to tell the front end if we are behind the sso proxy so when logout redirect to proxy logout" :type :boolean :default gosec-sso? :visibility :public diff --git a/src/metabase/stratio/header_user_info.clj b/src/metabase/stratio/header_user_info.clj index aaece0429b24a..d3959733f29db 100644 --- a/src/metabase/stratio/header_user_info.clj +++ b/src/metabase/stratio/header_user_info.clj @@ -43,7 +43,7 @@ (:alg (parse-header token))) -(defn- http-header->jwt-token +(defn- http-headers->jwt-token [headers] (let [header-name (st.config/config-str :jwt-header-name) header-name-lower (str/lower-case header-name)] @@ -52,47 +52,53 @@ (contains? headers header-name-lower) (get headers header-name-lower) :else (throw (Exception. "Could not find Authorization header"))))) + +(defn- http-cookies->jwt-token + [cookies] + (or (get-in cookies [st.config/jwt-cookie-name :value]) + (throw (Exception. (str "Could not find cookie '" st.config/jwt-cookie-name "'"))))) + + +(defn- http-request->jwt-token + [{:keys [headers cookies]}] + (cond + st.config/gosec-sso? (http-cookies->jwt-token cookies) + st.config/jwt? (http-headers->jwt-token headers))) + + (defn- verify-token [token pkey] (let [alg (get-alg token)] (jwt/unsign token pkey {:alg alg}))) -(defn- ssl-config [] - (if (st.config/config-bool :jwt-insecure-request-pkey) - {:insecure? true} - {:trust-store (config/config-str :mb-jetty-ssl-truststore) - :trust-store-pass (config/config-str :mb-jetty-ssl-truststore-password)})) - -(defn- get-verification-key - [url] - (-> url - (http/get (ssl-config)) - (:body) - (keys/str->public-key))) - - -(defn- http-headers->user-info-jwt - "Gets user info map {:user username :groups [group1 ... groupN]} from jwt token in headers - or map with :error key if some error happens" - [headers] +(defn- http-request->user-info-jwt + "Gets user info map {:user username, :groups [group1 ... groupN], :email email, :tenant tenant} + from jwt token in headers or in cookie. The :email and :tenant may not be present in the jwt. + If some error happens a map with an :error key is returned." + [request] (log/debug "Getting user info from JWT token") (try (let [username-claim (st.config/config-kw :jwt-username-claim) groups-claim (st.config/config-kw :jwt-groups-claim) - token (http-header->jwt-token headers) - pkey (get-verification-key (st.config/config-str :jwt-public-key-endpoint))] + email-claim (st.config/config-kw :jwt-email-claim) + tenant-claim (st.config/config-kw :jwt-tenant-claim) + token (http-request->jwt-token request) + pkey @st.config/jwt-public-key] (cond (not token) {:error "Could not obtain jwt token from request headers"} (not pkey) {:error "Could not obtain verification key for jwt token"} pkey (let [info (-> token (verify-token pkey) - (select-keys [username-claim groups-claim]) - (update-in [groups-claim] st.util/make-vector)) + (select-keys [username-claim groups-claim email-claim tenant-claim]) + (update-in [groups-claim] st.util/ensure-vector)) user-name (username-claim info)] (if (empty? user-name) {:error "No username claim found in token"} - {:user user-name :groups (groups-claim info)})))) + {:user user-name + :groups (groups-claim info) + :email (email-claim info) + :tenant (tenant-claim info)})))) (catch Exception e {:error (st.util/stack-trace e)}))) @@ -116,7 +122,7 @@ (defn http-headers->user-info "Gets user info map {:user username :groups [group1 ... groupN]} either form jwt token or headers depending on config; or map with :error key if some error happens" - [headers] - (if st.config/jwt? - (http-headers->user-info-jwt headers) - (http-headers->user-info-headers headers))) + [{headers :headers, :as request}] + (if st.config/headers? + (http-headers->user-info-headers headers) + (http-request->user-info-jwt request))) diff --git a/src/metabase/stratio/util.clj b/src/metabase/stratio/util.clj index 58038a023edfd..eb6c254fde1a8 100644 --- a/src/metabase/stratio/util.clj +++ b/src/metabase/stratio/util.clj @@ -12,5 +12,12 @@ (filterv #(not (empty? %)))))) +(defn ensure-vector + [string-or-coll] + (if (instance? java.lang.String string-or-coll) + (make-vector string-or-coll) + (vec string-or-coll))) + + (defn stack-trace [e] (with-out-str (clojure.stacktrace/print-stack-trace e)))