diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index 030ba2caa2ed71fecaab0891f2c4921018a461d0..7f68887e4202395fae513e1d2d41a715fb3ea515 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -12,7 +12,8 @@ [collection :as collection] [permissions :as perms] [permissions-group :as group] - [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]]] + [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]] + [session :refer [Session]]] [metabase.util [i18n :refer [trs]] [schema :as su]] @@ -229,6 +230,8 @@ [user-id password] (let [salt (str (UUID/randomUUID)) password (creds/hash-bcrypt (str salt password))] + ;; when changing/resetting the password, kill any existing sessions + (db/simple-delete! Session :user_id user-id) ;; NOTE: any password change expires the password reset token (db/update! User user-id :password_salt salt diff --git a/test/metabase/models/user_test.clj b/test/metabase/models/user_test.clj index 4873e7961f1c94e5133e5dc10219ca86ca4637b0..90431acc90266d9b206df44feeadfd3a76c69825 100644 --- a/test/metabase/models/user_test.clj +++ b/test/metabase/models/user_test.clj @@ -1,9 +1,11 @@ (ns metabase.models.user-test - (:require [clojure.string :as str] - [expectations :refer [expect]] + (:require [clojure + [string :as str] + [test :refer :all]] [metabase [email-test :as email-test] [http-client :as http] + [test :as mt] [util :as u]] [metabase.models [collection :as collection :refer [Collection]] @@ -11,57 +13,57 @@ [permissions :as perms] [permissions-group :as group :refer [PermissionsGroup]] [permissions-group-membership :refer [PermissionsGroupMembership]] + [session :refer [Session]] [user :as user :refer [User]]] [metabase.test.data.users :as test-users] - [metabase.test.util :as tu] [metabase.util.password :as u.password] [toucan [db :as db] - [hydrate :refer [hydrate]]] - [toucan.util.test :as tt])) + [hydrate :refer [hydrate]]])) ;;; Tests for permissions-set -;; Make sure the test users have valid permissions sets -(expect (perms/is-permissions-set? (user/permissions-set (test-users/user->id :rasta)))) -(expect (perms/is-permissions-set? (user/permissions-set (test-users/user->id :crowberto)))) -(expect (perms/is-permissions-set? (user/permissions-set (test-users/user->id :lucky)))) -(expect (perms/is-permissions-set? (user/permissions-set (test-users/user->id :trashbird)))) +(deftest check-test-users-have-valid-permissions-sets-test + (testing "Make sure the test users have valid permissions sets" + (doseq [user [:rasta :crowberto :lucky :trashbird]] + (testing user + (is (= true + (perms/is-permissions-set? (user/permissions-set (mt/user->id user))))))))) + +(deftest group-with-no-permissions-test + (testing (str "Adding a group with *no* permissions shouldn't suddenly break all the permissions sets (This was a " + "bug @tom found where a group with no permissions would cause the permissions set to contain `nil`).") + (mt/with-temp* [PermissionsGroup [{group-id :id}] + PermissionsGroupMembership [_ {:group_id group-id, :user_id (mt/user->id :rasta)}]] + (is (= true + (perms/is-permissions-set? (user/permissions-set (mt/user->id :rasta)))))))) -;; Ok, adding a group with *no* permissions shouldn't suddenly break all the permissions sets -;; (This was a bug @tom found where a group with no permissions would cause the permissions set to contain `nil`). -(expect - (tt/with-temp* [PermissionsGroup [{group-id :id}] - PermissionsGroupMembership [_ {:group_id group-id, :user_id (test-users/user->id :rasta)}]] - (perms/is-permissions-set? (user/permissions-set (test-users/user->id :rasta))))) - -;; Does permissions-set include permissions for my Personal Collection? (defn- remove-non-collection-perms [perms-set] (set (for [perms-path perms-set :when (str/starts-with? perms-path "/collection/")] perms-path))) -(expect - #{(perms/collection-readwrite-path (collection/user->personal-collection (test-users/user->id :lucky)))} - (tu/with-non-admin-groups-no-root-collection-perms - (-> (user/permissions-set (test-users/user->id :lucky)) - remove-non-collection-perms))) - -;; ...and for any descendant Collections of my Personal Collection? -(expect - #{(perms/collection-readwrite-path (collection/user->personal-collection (test-users/user->id :lucky))) - "/collection/child/" - "/collection/grandchild/"} - (tu/with-non-admin-groups-no-root-collection-perms - (tt/with-temp* [Collection [child-collection {:name "child" - :location (collection/children-location - (collection/user->personal-collection - (test-users/user->id :lucky)))}] - Collection [grandchild-collection {:name "grandchild" - :location (collection/children-location child-collection)}]] - (->> (user/permissions-set (test-users/user->id :lucky)) - remove-non-collection-perms - (collection-test/perms-path-ids->names [child-collection grandchild-collection]))))) +(deftest personal-collection-permissions-test + (testing "Does permissions-set include permissions for my Personal Collection?" + (mt/with-non-admin-groups-no-root-collection-perms + (is (= #{(perms/collection-readwrite-path (collection/user->personal-collection (mt/user->id :lucky)))} + (-> (user/permissions-set (mt/user->id :lucky)) + remove-non-collection-perms)))) + + (testing "...and for any descendant Collections of my Personal Collection?" + (mt/with-non-admin-groups-no-root-collection-perms + (mt/with-temp* [Collection [child-collection {:name "child" + :location (collection/children-location + (collection/user->personal-collection + (mt/user->id :lucky)))}] + Collection [grandchild-collection {:name "grandchild" + :location (collection/children-location child-collection)}]] + (is (= #{(perms/collection-readwrite-path (collection/user->personal-collection (mt/user->id :lucky))) + "/collection/child/" + "/collection/grandchild/"} + (->> (user/permissions-set (mt/user->id :lucky)) + remove-non-collection-perms + (collection-test/perms-path-ids->names [child-collection grandchild-collection]))))))))) ;;; Tests for invite-user and create-new-google-auth-user! @@ -91,11 +93,11 @@ and return a map of addresses emails were sent to to the email subjects." [& {:keys [google-auth? accept-invite? password invitor] :or {accept-invite? true}}] - (tu/with-temporary-setting-values [site-name "Metabase"] + (mt/with-temporary-setting-values [site-name "Metabase"] (email-test/with-fake-inbox - (let [new-user-email (tu/random-email) - new-user-first-name (tu/random-name) - new-user-last-name (tu/random-name) + (let [new-user-email (mt/random-email) + new-user-first-name (mt/random-name) + new-user-last-name (mt/random-name) new-user {:first_name new-user-first-name :last_name new-user-last-name :email new-user-email @@ -115,84 +117,81 @@ {:email "crowberto@metabase.com", :is_active true, :first_name "Crowberto"}) ;; admin shouldn't get email saying user joined until they accept the invite (i.e., reset their password) -(expect - {"<New User>" ["You're invited to join Metabase's Metabase"]} - (invite-user-accept-and-check-inboxes! :invitor default-invitor, :accept-invite? false)) - -;; admin should get an email when a new user joins... -(expect - {"<New User>" ["You're invited to join Metabase's Metabase"] - "crowberto@metabase.com" ["<New User> accepted their Metabase invite"]} - (invite-user-accept-and-check-inboxes! :invitor default-invitor)) - -;; ...including the site admin if it is set... -(expect - {"<New User>" ["You're invited to join Metabase's Metabase"] - "crowberto@metabase.com" ["<New User> accepted their Metabase invite"] - "cam@metabase.com" ["<New User> accepted their Metabase invite"]} - (tu/with-temporary-setting-values [admin-email "cam@metabase.com"] - (invite-user-accept-and-check-inboxes! :invitor default-invitor))) - -;; ... but if that admin is inactive they shouldn't get an email -(expect - {"<New User>" ["You're invited to join Metabase's Metabase"] - "crowberto@metabase.com" ["<New User> accepted their Metabase invite"]} - (tt/with-temp User [inactive-admin {:is_superuser true, :is_active false}] - (invite-user-accept-and-check-inboxes! :invitor (assoc inactive-admin :is_active false)))) - -;; for google auth, all admins should get an email... -(expect - {"crowberto@metabase.com" ["<New User> created a Metabase account"] - "some_other_admin@metabase.com" ["<New User> created a Metabase account"]} - (tt/with-temp User [_ {:is_superuser true, :email "some_other_admin@metabase.com"}] - (invite-user-accept-and-check-inboxes! :google-auth? true))) - -;; ...including the site admin if it is set... -(expect - {"crowberto@metabase.com" ["<New User> created a Metabase account"] - "some_other_admin@metabase.com" ["<New User> created a Metabase account"] - "cam@metabase.com" ["<New User> created a Metabase account"]} - (tu/with-temporary-setting-values [admin-email "cam@metabase.com"] - (tt/with-temp User [_ {:is_superuser true, :email "some_other_admin@metabase.com"}] - (invite-user-accept-and-check-inboxes! :google-auth? true)))) - -;; ...unless they are inactive -(expect - {"crowberto@metabase.com" ["<New User> created a Metabase account"]} - (tt/with-temp User [_ {:is_superuser true, :is_active false}] - (invite-user-accept-and-check-inboxes! :google-auth? true))) - -;; LDAP users should not persist their passwords. Check that if somehow we get passed an LDAP user password, it gets -;; swapped with something random -(expect - false - (try - (user/create-new-ldap-auth-user! {:email "ldaptest@metabase.com" - :first_name "Test" - :last_name "SomeLdapStuff" - :password "should be removed"}) - (let [{:keys [password password_salt]} (db/select-one [User :password :password_salt] :email "ldaptest@metabase.com")] - (u.password/verify-password "should be removed" password_salt password)) - (finally - (db/delete! User :email "ldaptest@metabase.com")))) - -;; when you create a new user with `is_superuser` set to `true`, it should create a PermissionsGroupMembership object -(expect - true - (tt/with-temp User [user {:is_superuser true}] - (db/exists? PermissionsGroupMembership :user_id (u/get-id user), :group_id (u/get-id (group/admin))))) - -;; You should be able to create a new LDAP user if some `login_attributes` are vectors (#10291) -(expect - {"local_birds" ["Steller's Jay" "Mountain Chickadee"]} - (try - (user/create-new-ldap-auth-user! {:email "ldaptest@metabase.com" - :first_name "Test" - :last_name "SomeLdapStuff" - :login_attributes {:local_birds ["Steller's Jay" "Mountain Chickadee"]}}) - (db/select-one-field :login_attributes User :email "ldaptest@metabase.com") - (finally - (db/delete! User :email "ldaptest@metabase.com")))) + +(deftest new-user-emails-test + (testing "New user should get an invite email" + (is (= {"<New User>" ["You're invited to join Metabase's Metabase"]} + (invite-user-accept-and-check-inboxes! :invitor default-invitor, :accept-invite? false)))) + + (testing "admin should get an email when a new user joins..." + (is (= {"<New User>" ["You're invited to join Metabase's Metabase"] + "crowberto@metabase.com" ["<New User> accepted their Metabase invite"]} + (invite-user-accept-and-check-inboxes! :invitor default-invitor))) + + (testing "...including the site admin if it is set..." + (is (= {"<New User>" ["You're invited to join Metabase's Metabase"] + "crowberto@metabase.com" ["<New User> accepted their Metabase invite"] + "cam@metabase.com" ["<New User> accepted their Metabase invite"]} + (mt/with-temporary-setting-values [admin-email "cam@metabase.com"] + (invite-user-accept-and-check-inboxes! :invitor default-invitor)))) + + (testing "... but if that admin is inactive they shouldn't get an email" + (is (= {"<New User>" ["You're invited to join Metabase's Metabase"] + "crowberto@metabase.com" ["<New User> accepted their Metabase invite"]} + (mt/with-temp User [inactive-admin {:is_superuser true, :is_active false}] + (invite-user-accept-and-check-inboxes! :invitor (assoc inactive-admin :is_active false)))))))) + + (testing "for google auth, all admins should get an email..." + (is (= {"crowberto@metabase.com" ["<New User> created a Metabase account"] + "some_other_admin@metabase.com" ["<New User> created a Metabase account"]} + (mt/with-temp User [_ {:is_superuser true, :email "some_other_admin@metabase.com"}] + (invite-user-accept-and-check-inboxes! :google-auth? true)))) + + (testing "...including the site admin if it is set..." + (is (= {"crowberto@metabase.com" ["<New User> created a Metabase account"] + "some_other_admin@metabase.com" ["<New User> created a Metabase account"] + "cam@metabase.com" ["<New User> created a Metabase account"]} + (mt/with-temporary-setting-values [admin-email "cam@metabase.com"] + (mt/with-temp User [_ {:is_superuser true, :email "some_other_admin@metabase.com"}] + (invite-user-accept-and-check-inboxes! :google-auth? true))))) + + (testing "...unless they are inactive" + (is (= {"crowberto@metabase.com" ["<New User> created a Metabase account"]} + (mt/with-temp User [_ {:is_superuser true, :is_active false}] + (invite-user-accept-and-check-inboxes! :google-auth? true)))))))) + +(deftest ldap-user-passwords-test + (testing (str "LDAP users should not persist their passwords. Check that if somehow we get passed an LDAP user " + "password, it gets swapped with something random") + (try + (user/create-new-ldap-auth-user! {:email "ldaptest@metabase.com" + :first_name "Test" + :last_name "SomeLdapStuff" + :password "should be removed"}) + (let [{:keys [password password_salt]} (db/select-one [User :password :password_salt] :email "ldaptest@metabase.com")] + (is (= false + (u.password/verify-password "should be removed" password_salt password)))) + (finally + (db/delete! User :email "ldaptest@metabase.com"))))) + +(deftest new-admin-user-test + (testing (str "when you create a new user with `is_superuser` set to `true`, it should create a " + "PermissionsGroupMembership object") + (mt/with-temp User [user {:is_superuser true}] + (is (= true + (db/exists? PermissionsGroupMembership :user_id (u/get-id user), :group_id (u/get-id (group/admin)))))))) + +(deftest ldap-sequential-login-attributes-test + (testing "You should be able to create a new LDAP user if some `login_attributes` are vectors (#10291)" + (try + (user/create-new-ldap-auth-user! {:email "ldaptest@metabase.com" + :first_name "Test" + :last_name "SomeLdapStuff" + :login_attributes {:local_birds ["Steller's Jay" "Mountain Chickadee"]}}) + (is (= {"local_birds" ["Steller's Jay" "Mountain Chickadee"]} + (db/select-one-field :login_attributes User :email "ldaptest@metabase.com"))) + (finally + (db/delete! User :email "ldaptest@metabase.com"))))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -204,12 +203,12 @@ (db/select-field :name PermissionsGroup :id [:in (map u/get-id groups-or-ids)]))) (defn- do-with-group [group-properties group-members f] - (tt/with-temp PermissionsGroup [group group-properties] + (mt/with-temp PermissionsGroup [group group-properties] (doseq [member group-members] (db/insert! PermissionsGroupMembership {:group_id (u/get-id group) :user_id (if (keyword? member) - (test-users/user->id member) + (mt/user->id member) (u/get-id member))})) (f group))) @@ -220,159 +219,168 @@ ~@body)) `(do-with-group ~group-properties ~members (fn [~group-binding] ~@body)))) -;; make sure that the `group-ids` hydration function works as expected -(expect - #{"All Users" "Group 2" "Group 1"} - (with-groups [_ {:name "Group 1"} #{:lucky :rasta} - _ {:name "Group 2"} #{:lucky} - _ {:name "Group 3"} #{}] - (group-names (user/group-ids (test-users/user->id :lucky))))) - -;; `group-ids` should be a single DB call -(expect - 1 - (with-groups [_ {:name "Group 1"} #{:lucky} - _ {:name "Group 2"} #{:lucky} - _ {:name "Group 3"} #{}] - (let [lucky-id (test-users/user->id :lucky)] - (db/with-call-counting [call-count] - (user/group-ids lucky-id) - (call-count))))) - -;; `group-ids` shouldn't barf if passed `nil` -(expect - nil - (user/group-ids nil)) - -;; check that the `add-group-ids` hydration function can do a batched hydrate -(expect - {"Lucky" #{"All Users" "Group 1" "Group 2"} - "Rasta" #{"All Users" "Group 1"}} - (with-groups [_ {:name "Group 1"} #{:lucky :rasta} - _ {:name "Group 2"} #{:lucky} - _ {:name "Group 3"} #{}] - (let [users (user/add-group-ids (map test-users/fetch-user [:lucky :rasta]))] - (zipmap (map :first_name users) - (map (comp group-names :group_ids) users))))) - -;; `add-group-ids` should be the hydrate function for a `:group_ids` for a single User -(expect - '(user/add-group-ids <users>) - (with-redefs [user/group-ids (constantly '(user/group-ids <user>)) - user/add-group-ids (fn [users] - (for [user users] - (assoc user :group_ids '(user/add-group-ids <users>))))] - (-> (hydrate (User (test-users/user->id :lucky)) :group_ids) - :group_ids))) - -;; `add-group-ids` should be the batched hydrate function for a `:group_ids` -;; (Toucan can/will use batched hydrate functions to hydrate single objects) -(expect - '[(user/add-group-ids <users>) - (user/add-group-ids <users>)] - (with-redefs [user/group-ids (constantly '(user/group-ids <user>)) - user/add-group-ids (fn [users] - (for [user users] - (assoc user :group_ids '(user/add-group-ids <users>))))] - (as-> (map test-users/fetch-user [:rasta :lucky]) users - (hydrate users :group_ids) - (mapv :group_ids users)))) - -;; ...it should do it in a single DB call -(expect - 1 - (with-groups [_ {:name "Group 1"} #{:lucky :rasta} - _ {:name "Group 2"} #{:lucky} - _ {:name "Group 3"} #{}] - (let [users (mapv test-users/fetch-user [:lucky :rasta])] - (db/with-call-counting [call-count] - (dorun (user/add-group-ids users)) - (call-count))))) - -;; `add-group-ids` shouldn't barf if passed an empty seq -(expect - nil - (user/add-group-ids [])) +(deftest group-ids-test + (testing "the `group-ids` hydration function" + (testing "should work as expected" + (with-groups [_ {:name "Group 1"} #{:lucky :rasta} + _ {:name "Group 2"} #{:lucky} + _ {:name "Group 3"} #{}] + (is (= #{"All Users" "Group 2" "Group 1"} + (group-names (user/group-ids (mt/user->id :lucky))))))) + + (testing "should be a single DB call" + (with-groups [_ {:name "Group 1"} #{:lucky} + _ {:name "Group 2"} #{:lucky} + _ {:name "Group 3"} #{}] + (let [lucky-id (mt/user->id :lucky)] + (db/with-call-counting [call-count] + (user/group-ids lucky-id) + (is (= 1 + (call-count))))))) + + (testing "shouldn't barf if passed `nil`" + (is (= nil + (user/group-ids nil)))))) + +(deftest add-group-ids-test + (testing "the `add-group-ids` hydration function" + (testing "should do a batched hydate" + (with-groups [_ {:name "Group 1"} #{:lucky :rasta} + _ {:name "Group 2"} #{:lucky} + _ {:name "Group 3"} #{}] + (let [users (user/add-group-ids (map test-users/fetch-user [:lucky :rasta]))] + (is (= {"Lucky" #{"All Users" "Group 1" "Group 2"} + "Rasta" #{"All Users" "Group 1"}} + (zipmap (map :first_name users) + (map (comp group-names :group_ids) users))))))) + + (testing "should be the hydrate function for `:group_ids`" + (with-redefs [user/group-ids (constantly '(user/group-ids <user>)) + user/add-group-ids (fn [users] + (for [user users] + (assoc user :group_ids '(user/add-group-ids <users>))))] + (testing "for a single User" + (is (= '(user/add-group-ids <users>) + (-> (hydrate (User (mt/user->id :lucky)) :group_ids) + :group_ids)))) + + (testing "for multiple Users" + (is (= '[(user/add-group-ids <users>) + (user/add-group-ids <users>)] + (as-> (map test-users/fetch-user [:rasta :lucky]) users + (hydrate users :group_ids) + (mapv :group_ids users))))))) + + (testing "should be done in a single DB call" + (with-groups [_ {:name "Group 1"} #{:lucky :rasta} + _ {:name "Group 2"} #{:lucky} + _ {:name "Group 3"} #{}] + (let [users (mapv test-users/fetch-user [:lucky :rasta])] + (db/with-call-counting [call-count] + (dorun (user/add-group-ids users)) + (is (= 1 + (call-count))))))) + + (testing "shouldn't barf if passed an empty seq" + (is (= nil + (user/add-group-ids [])))))) (defn user-group-names [user-or-id-or-kw] (group-names (user/group-ids (if (keyword? user-or-id-or-kw) (test-users/fetch-user user-or-id-or-kw) user-or-id-or-kw)))) -;; check that we can use `set-permissions-groups!` to add a User to new groups -(expect - #{"All Users" "Group 1" "Group 2"} - (with-groups [group-1 {:name "Group 1"} #{} - group-2 {:name "Group 2"} #{}] - (user/set-permissions-groups! (test-users/user->id :lucky) #{(group/all-users) group-1 group-2}) - (user-group-names :lucky))) - -;; check that we can use `set-permissions-groups!` to remove a User from groups -(expect - #{"All Users"} - (with-groups [group-1 {:name "Group 1"} #{:lucky} - group-2 {:name "Group 2"} #{:lucky}] - (user/set-permissions-groups! (test-users/user->id :lucky) #{(group/all-users)}) - (user-group-names :lucky))) - -;; check that `set-permissions-groups!` can add & remove groups all at once! :wow: -(expect - #{"All Users" "Group 2"} - (with-groups [group-1 {:name "Group 1"} #{:lucky} - group-2 {:name "Group 2"} #{}] - (user/set-permissions-groups! (test-users/user->id :lucky) #{(group/all-users) group-2}) - (user-group-names :lucky))) - -;; `set-permissions-groups!` should throw an Exception if you attempt to remove someone from All Users -(expect - Exception - (with-groups [group-1 {:name "Group 1"} #{}] - (user/set-permissions-groups! (test-users/user->id :lucky) #{group-1}))) - -;; `set-permissions-groups!` should let someone be added to Admin group -(expect - #{"Administrators" "All Users"} - (tt/with-temp User [user] - (user/set-permissions-groups! user #{(group/all-users) (group/admin)}) - (user-group-names user))) - -;; is_superuser should get set when adding a user to admin via `set-permissions-groups!` -(expect - {:is_superuser true} - (tt/with-temp User [user] - (user/set-permissions-groups! user #{(group/all-users) (group/admin)}) - (db/select-one [User :is_superuser] :id (u/get-id user)))) - -;; `set-permissions-groups!` should let someone be removed from Admin group -(expect - #{"All Users"} - (tt/with-temp User [user {:is_superuser true}] - (user/set-permissions-groups! user #{(group/all-users)}) - (user-group-names user))) - -(expect - false - (tt/with-temp User [user {:is_superuser true}] - (user/set-permissions-groups! user #{(group/all-users)}) - (db/select-one-field :is_superuser User :id (u/get-id user)))) - -;; The entire set of changes should run in a transaction -- if one set of changes fails, others should not be persisted -;; [INVALID ADD] -(expect - true - ;; User should not be removed from the admin group because the attempt to add them to the Integer/MAX_VALUE group - ;; should fail, causing the entire transaction to fail - (tt/with-temp User [user {:is_superuser true}] - (u/ignore-exceptions - (user/set-permissions-groups! user #{(group/all-users) Integer/MAX_VALUE})) - (db/select-one-field :is_superuser User :id (u/get-id user)))) - -;; If an INVALID REMOVE is attempted, valid adds should not be persisted -;; Attempt to remove someone from All Users + add to a valid group at the same time -- neither should persist -(expect - #{"All Users"} - (tt/with-temp User [user] - (with-groups [group {:name "Group"} {}] - (u/ignore-exceptions - (user/set-permissions-groups! (test-users/fetch-user :lucky) #{group}))) - (user-group-names :lucky))) +(deftest set-permissions-groups-test + (testing "set-permissions-groups!" + (testing "should be able to add a User to new groups" + (with-groups [group-1 {:name "Group 1"} #{} + group-2 {:name "Group 2"} #{}] + (user/set-permissions-groups! (mt/user->id :lucky) #{(group/all-users) group-1 group-2}) + (is (= #{"All Users" "Group 1" "Group 2"} + (user-group-names :lucky))))) + + (testing "should be able to remove a User from groups" + (with-groups [group-1 {:name "Group 1"} #{:lucky} + group-2 {:name "Group 2"} #{:lucky}] + (user/set-permissions-groups! (mt/user->id :lucky) #{(group/all-users)}) + (is (= #{"All Users"} + (user-group-names :lucky))))) + + (testing "should be able to add & remove groups at the same time! :wow:" + (with-groups [group-1 {:name "Group 1"} #{:lucky} + group-2 {:name "Group 2"} #{}] + (user/set-permissions-groups! (mt/user->id :lucky) #{(group/all-users) group-2}) + (is (= #{"All Users" "Group 2"} + (user-group-names :lucky))))) + + (testing "should throw an Exception if you attempt to remove someone from All Users" + (with-groups [group-1 {:name "Group 1"} #{}] + (is (thrown? Exception + (user/set-permissions-groups! (mt/user->id :lucky) #{group-1}))))) + + (testing "should be able to add someone to the Admin group" + (mt/with-temp User [user] + (user/set-permissions-groups! user #{(group/all-users) (group/admin)}) + (is (= #{"Administrators" "All Users"} + (user-group-names user))) + + (testing "their is_superuser flag should be set to true" + (is (= true + (db/select-one-field :is_superuser User :id (u/get-id user))))))) + + (testing "should be able to remove someone from the Admin group" + (mt/with-temp User [user {:is_superuser true}] + (user/set-permissions-groups! user #{(group/all-users)}) + (is (= #{"All Users"} + (user-group-names user))) + + (testing "their is_superuser flag should be set to false" + (is (= false + (db/select-one-field :is_superuser User :id (u/get-id user))))))) + + (testing "should run all changes in a transaction -- if one set of changes fails, others should not be persisted" + (testing "Invalid ADD operation" + ;; User should not be removed from the admin group because the attempt to add them to the Integer/MAX_VALUE group + ;; should fail, causing the entire transaction to fail + (mt/with-temp User [user {:is_superuser true}] + (u/ignore-exceptions + (user/set-permissions-groups! user #{(group/all-users) Integer/MAX_VALUE})) + (is (= true + (db/select-one-field :is_superuser User :id (u/get-id user)))))) + + (testing "Invalid REMOVE operation" + ;; Attempt to remove someone from All Users + add to a valid group at the same time -- neither should persist + (mt/with-temp User [user] + (with-groups [group {:name "Group"} {}] + (u/ignore-exceptions + (user/set-permissions-groups! (test-users/fetch-user :lucky) #{group}))) + (is (= #{"All Users"} + (user-group-names :lucky)) + "If an INVALID REMOVE is attempted, valid adds should not be persisted")))))) + +(deftest set-password-test + (testing "set-password!" + (testing "should change the password" + (mt/with-temp User [{user-id :id} {:password "ABC_DEF"}] + (letfn [(password [] (db/select-one-field :password User :id user-id))] + (let [original-password (password)] + (user/set-password! user-id "p@ssw0rd") + (is (not= original-password + (password))))))) + + (testing "should clear out password reset token" + (mt/with-temp User [{user-id :id} {:reset_token "ABC123"}] + (user/set-password! user-id "p@ssw0rd") + (is (= nil + (db/select-one-field :reset_token User :id user-id))))) + + (testing "should clear out all existing Sessions" + (mt/with-temp* [User [{user-id :id}] + Session [_ {:id (str (java.util.UUID/randomUUID)), :user_id user-id}] + Session [_ {:id (str (java.util.UUID/randomUUID)), :user_id user-id}]] + (letfn [(session-count [] (db/count Session :user_id user-id))] + (is (= 2 + (session-count))) + (user/set-password! user-id "p@ssw0rd") + (is (= 0 + (session-count))))))))