diff --git a/.clj-kondo/hooks/metabase/models/setting.clj b/.clj-kondo/hooks/metabase/models/setting.clj index 4ec17037ea6b359de08a334c7be929fb04d33415..4c64701a4bb9c33c3994c10b9065595d0659613c 100644 --- a/.clj-kondo/hooks/metabase/models/setting.clj +++ b/.clj-kondo/hooks/metabase/models/setting.clj @@ -62,6 +62,7 @@ jwt-group-mappings jwt-group-sync jwt-identity-provider-uri + jwt-user-provisioning-enabled? jwt-shared-secret last-acknowledged-version ldap-attribute-email @@ -77,6 +78,7 @@ ldap-host ldap-password ldap-port + ldap-user-provisioning-enabled? ldap-security ldap-sync-user-attributes ldap-sync-user-attributes-blacklist @@ -128,6 +130,7 @@ saml-keystore-alias saml-keystore-password saml-keystore-path + saml-user-provisioning-enabled? send-email-on-first-login-from-new-device send-new-sso-user-admin-email? session-cookie-samesite diff --git a/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj b/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj index 01b284c4fa6152ccedd8b78cff8c23746cca738f..cae4a48bc4c5fab80bf81bf8cbf7d8c7ca0f6820 100644 --- a/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj +++ b/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj @@ -1,6 +1,7 @@ (ns metabase-enterprise.enhancements.integrations.ldap "The Enterprise version of the LDAP integration is basically the same but also supports syncing user attributes." (:require + [metabase-enterprise.sso.integrations.sso-utils :as sso-utils] [metabase.integrations.common :as integrations.common] [metabase.integrations.ldap.default-implementation :as default-impl] [metabase.models.setting :as setting :refer [defsetting]] @@ -83,6 +84,7 @@ [{:keys [first-name last-name email groups attributes], :as user-info} :- EEUserInfo {:keys [sync-groups?], :as settings} :- default-impl/LDAPSettings] (let [user (or (attribute-synced-user user-info) + (sso-utils/check-user-provisioning :ldap) (-> (user/create-new-ldap-auth-user! {:first_name first-name :last_name last-name :email email diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj index 02045148b0392e61b77fec0fb7db8cbd8dc6105f..0fd300419a1a891989033e19fefe8a536d26881c 100644 --- a/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj +++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj @@ -31,6 +31,7 @@ :sso_source :jwt :login_attributes user-attributes}] (or (sso-utils/fetch-and-update-login-attributes! user) + (sso-utils/check-user-provisioning :jwt) (sso-utils/create-new-sso-user! user)))) (def ^:private ^{:arglists '([])} jwt-attribute-email (comp keyword sso-settings/jwt-attribute-email)) diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj index 90ace6e59cacd7cbbe1e21612df8a1e8ae526fcb..fc1d4cc318a5b4e48a3f28ead022f89ea27cadbc 100644 --- a/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj +++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj @@ -87,6 +87,7 @@ :sso_source :saml :login_attributes user-attributes}] (when-let [user (or (sso-utils/fetch-and-update-login-attributes! new-user) + (sso-utils/check-user-provisioning :saml) (sso-utils/create-new-sso-user! new-user))] (sync-groups! user group-names) (api.session/create-session! :sso user device-info)))) diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj index e7ec136a2f51540fc23ebb134423abdb36e45acb..5024e8bbf9fce050495cf64e0dded962500ae365 100644 --- a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj +++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj @@ -23,6 +23,29 @@ (def ^:private ^{:arglists '([group-mappings])} validate-group-mappings (mc/validator GroupMappings)) +(defsetting saml-user-provisioning-enabled? + (deferred-tru "When we enable SAML user provisioning, we automatically create a Metabase account on SAML signin for users who +don''t have one.") + :type :boolean + :default true + :feature :sso-saml + :audit :getter) + +(defsetting jwt-user-provisioning-enabled? + (deferred-tru "When we enable JWT user provisioning, we automatically create a Metabase account on JWT signin for users who +don''t have one.") + :type :boolean + :default true + :feature :sso-jwt + :audit :getter) + +(defsetting ldap-user-provisioning-enabled? + (deferred-tru "When we enable LDAP user provisioning, we automatically create a Metabase account on LDAP signin for users who +don''t have one.") + :type :boolean + :default true + :audit :getter) + (defsetting saml-identity-provider-uri (deferred-tru "This is the URL where your users go to log in to your identity provider. Depending on which IdP you''re using, this usually looks like https://your-org-name.example.com or https://example.com/app/my_saml_app/abc123/sso/saml") diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj index a65b122ee31bf5eb7bee2e0982152792f53b14c8..f471e8a7e2f9280473857e43960dcc254fa2729b 100644 --- a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj +++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj @@ -1,6 +1,7 @@ (ns metabase-enterprise.sso.integrations.sso-utils "Functions shared by the various SSO implementations" (:require + [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] [metabase.api.common :as api] [metabase.email.messages :as messages] [metabase.events :as events] @@ -29,10 +30,33 @@ [: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))) {})))) + +(defmulti check-user-provisioning + "If `user-provisioning-enabled?` is false, then we should throw an error when attempting to create a new user." + {: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?))) + (mu/defn create-new-sso-user! "This function is basically the same thing as the `create-new-google-auth-user` from `metabase.models.user`. We need to refactor the `core_user` table structure and the function used to populate it so that the enterprise product can - reuse it" + reuse it." [user :- UserAttributes] (try (u/prog1 (first (t2/insert-returning-instances! User (merge user {:password (str (random-uuid))}))) diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj index 01508c6c26c8996bfc701f96bde9981e72197c94..9f9da6588e5d22ec5c4bd4b690b2df017c72d8fa 100644 --- a/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj +++ b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj @@ -2,8 +2,10 @@ (:require [clojure.test :refer :all] [metabase-enterprise.enhancements.integrations.ldap :as ldap-ee] + [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] [metabase.integrations.ldap :as ldap] [metabase.models.user :as user :refer [User]] + [metabase.public-settings :as public-settings] [metabase.test :as mt] [metabase.test.integrations.ldap :as ldap.test] [metabase.util.malli.schema :as ms] @@ -247,3 +249,14 @@ :common_name "jane.miller@metabase.com"} (select-keys (t2/select-one User :email "jane.miller@metabase.com") [:first_name :last_name :common_name])))) (finally (t2/delete! User :email "jane.miller@metabase.com")))))) + +(deftest ldap-no-user-provisioning-test + (mt/with-premium-features #{:sso-ldap} + (ldap.test/with-ldap-server + (testing "an error is thrown when a new user is fetched and user provisioning is not enabled" + (with-redefs [sso-settings/ldap-user-provisioning-enabled? (constantly false) + public-settings/site-name (constantly "test")] + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Sorry, but you'll need a test account to view this page. Please contact your administrator." + (ldap/fetch-or-create-user! (ldap/find-user "jsmith1"))))))))) diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj index b545755fba8379dbd96ae5a7b8f6efcce1c4483e..e4dc77137cd20ea6a6d5194d8b674dd69c0b0025 100644 --- a/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj @@ -7,11 +7,13 @@ [crypto.random :as crypto-random] [metabase-enterprise.sso.integrations.jwt :as mt.jwt] [metabase-enterprise.sso.integrations.saml-test :as saml-test] + [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] [metabase.config :as config] [metabase.models.permissions-group :refer [PermissionsGroup]] [metabase.models.permissions-group-membership :refer [PermissionsGroupMembership]] [metabase.models.user :refer [User]] + [metabase.public-settings :as public-settings] [metabase.public-settings.premium-features :as premium-features] [metabase.test :as mt] [metabase.test.fixtures :as fixtures] @@ -294,3 +296,14 @@ (is (= #{"All Users" ":metabase-enterprise.sso.integrations.jwt-test/my-group"} (group-memberships (u/the-id (t2/select-one-pk User :email "newuser@metabase.com")))))))))))) + +(deftest create-new-jwt-user-no-user-provisioning-test + (testing "When user provisioning is disabled, throw an error if we attempt to create a new user." + (with-jwt-default-setup + (with-redefs [sso-settings/jwt-user-provisioning-enabled? (constantly false) + public-settings/site-name (constantly "test")] + (is + (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Sorry, but you'll need a test account to view this page. Please contact your administrator." + (#'mt.jwt/fetch-or-create-user! "Test" "User" "test1234@metabase.com" nil))))))) diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj index 78052d99bde15d5dd98c2c8609f46aa278440fcd..cf9bbce18b9108fa91740e0d222db8abe03b4541 100644 --- a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj @@ -3,6 +3,7 @@ [clojure.set :as set] [clojure.string :as str] [clojure.test :refer :all] + [metabase-enterprise.sso.integrations.saml :as saml.mt] [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] [metabase.http-client :as client] [metabase.models.permissions-group :refer [PermissionsGroup]] @@ -636,3 +637,16 @@ (is (successful-login? response)) (is (= (str "http://localhost:3001/path" redirect-url) (get-in response [:headers "Location"]))))))))))))))) + +(deftest create-new-saml-user-no-user-provisioning-test + (testing "When user provisioning is disabled, throw an error if we attempt to create a new user." + (with-saml-default-setup + (with-redefs [sso-settings/saml-user-provisioning-enabled? (constantly false) + public-settings/site-name (constantly "test")] + (is + (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Sorry, but you'll need a test account to view this page. Please contact your administrator." + (#'saml.mt/fetch-or-create-user! {:first-name "Test" + :last-name "user" + :email "test1234@metabsae.com"})))))))