From 62eb3c71caa61fa5060391b84d668baeb8ef4a69 Mon Sep 17 00:00:00 2001
From: Ngoc Khuat <qn.khuat@gmail.com>
Date: Tue, 12 Apr 2022 21:33:58 +0700
Subject: [PATCH] Enforce Setting Permissions for sharing APIs (#21610)

* disallow adding recipients by non-admins with monitoring permission

* enforce sharing and embedding apis

Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com>
---
 .../advanced_permissions/api/setting_test.clj | 96 ++++++++++++++++++-
 src/metabase/api/card.clj                     |  8 +-
 src/metabase/api/common/validation.clj        |  6 +-
 src/metabase/api/dashboard.clj                |  6 +-
 test/metabase/api/card_test.clj               | 15 ++-
 test/metabase/api/dashboard_test.clj          |  9 +-
 6 files changed, 123 insertions(+), 17 deletions(-)

diff --git a/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/setting_test.clj b/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/setting_test.clj
index 2edff32c469..7dfc574085f 100644
--- a/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/setting_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/setting_test.clj
@@ -5,12 +5,14 @@
             [metabase.api.ldap-test :as ldap-test]
             [metabase.email :as email]
             [metabase.integrations.slack :as slack]
+            [metabase.models :refer [Card Dashboard]]
             [metabase.models.permissions :as perms]
             [metabase.models.setting-test :refer [test-setting-1 test-setting-2]]
             [metabase.public-settings.premium-features-test :as premium-features-test]
             [metabase.test :as mt]
             [metabase.test.fixtures :as fixtures]
-            [metabase.test.integrations.ldap :as ldap.test]))
+            [metabase.test.integrations.ldap :as ldap.test])
+  (:import java.util.UUID))
 
 (use-fixtures :once (fixtures/initialize :db))
 
@@ -238,3 +240,95 @@
               (perms/grant-general-permissions! group :setting)
               (get-permission-groups user 200)
               (get-permission-groups :crowberto 200))))))))
+
+(deftest dashboard-api-test
+  (testing "/api/dashboard"
+    (mt/with-temporary-setting-values [enable-public-sharing true
+                                       enable-embedding      true]
+      (mt/with-user-in-groups
+        [group {:name "New Group"}
+         user  [group]]
+        (letfn [(get-public-dashboards [user status]
+                  (testing (format "get public dashboards with %s user" (user->name user))
+                    (mt/user-http-request user :get status "dashboard/public")))
+
+                (get-embeddable-dashboards [user status]
+                  (testing (format "get embeddable dashboards with %s user" (user->name user))
+                    (mt/with-temp Dashboard [_ {:enable_embedding true}]
+                      (mt/user-http-request user :get status "dashboard/embeddable"))))
+
+                (delete-public-dashboard [user status]
+                  (testing (format "delete public dashboard with %s user" (user->name user))
+                    (mt/with-temp Dashboard [{dashboard-id :id} {:public_uuid       (str (UUID/randomUUID))
+                                                                 :made_public_by_id (mt/user->id :crowberto)}]
+                      (mt/user-http-request user :delete status (format "dashboard/%d/public_link" dashboard-id)))))]
+
+          (testing "if `advanced-permissions` is disabled, require admins,"
+            (premium-features-test/with-premium-features #{}
+              (get-public-dashboards user 403)
+              (get-embeddable-dashboards user 403)
+              (delete-public-dashboard user 403)
+              (get-embeddable-dashboards :crowberto 200)
+              (delete-public-dashboard :crowberto 204)))
+
+          (testing "if `advanced-permissions` is enabled,"
+            (premium-features-test/with-premium-features #{:advanced-permissions}
+              (testing "still fail if user's group doesn't have `setting` permission"
+                (get-public-dashboards user 403)
+                (get-embeddable-dashboards user 403)
+                (delete-public-dashboard user 403)
+                (get-public-dashboards :crowberto 200)
+                (delete-public-dashboard :crowberto 204))
+
+              (testing "succeed if user's group has `setting` permission,"
+                (perms/grant-general-permissions! group :setting)
+                (get-public-dashboards user 200)
+                (get-embeddable-dashboards user 200)
+                (delete-public-dashboard user 204)))))))))
+
+(deftest card-api-test
+  (testing "/api/card"
+    (mt/with-temporary-setting-values [enable-public-sharing true
+                                       enable-embedding      true]
+      (mt/with-user-in-groups
+        [group {:name "New Group"}
+         user  [group]]
+        (letfn [(get-public-cards [user status]
+                  (testing (format "get public cards with %s user" (user->name user))
+                    (mt/user-http-request user :get status "card/public")))
+
+                (get-embeddable-cards [user status]
+                  (testing (format "get embeddable dashboards with %s user" (user->name user))
+                    (mt/with-temp Card[_ {:enable_embedding true}]
+                      (mt/user-http-request user :get status "card/embeddable"))))
+
+                (delete-public-card [user status]
+                  (testing (format "delete public card with %s user" (user->name user))
+                    (mt/with-temp Card [{card-id :id} {:public_uuid       (str (UUID/randomUUID))
+                                                       :made_public_by_id (mt/user->id :crowberto)}]
+                      (mt/user-http-request user :delete status (format "card/%d/public_link" card-id)))))]
+
+          (testing "if `advanced-permissions` is disabled, require admins,"
+            (premium-features-test/with-premium-features #{}
+              (get-public-cards user 403)
+              (get-embeddable-cards user 403)
+              (delete-public-card user 403)
+              (get-public-cards :crowberto 200)
+              (get-embeddable-cards :crowberto 200)
+              (delete-public-card :crowberto 204)))
+
+          (testing "if `advanced-permissions` is enabled"
+            (premium-features-test/with-premium-features #{:advanced-permissions}
+              (testing "still fail if user's group doesn't have `setting` permission,"
+                (get-public-cards user 403)
+                (get-embeddable-cards user 403)
+                (delete-public-card user 403)
+                (get-public-cards :crowberto 200)
+                (get-embeddable-cards :crowberto 200)
+                (delete-public-card :crowberto 204))
+
+              (testing "succeed if user's group has `setting` permission,"
+                (perms/grant-general-permissions! group :setting)
+                (get-public-cards user 200)
+                (get-embeddable-cards user 200)
+                (delete-public-card user 204)))))))))
diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index 2f58a724264..cfa75db4282 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -698,7 +698,7 @@
   already been shared, it will return the existing public link rather than creating a new one.)  Public sharing must
   be enabled."
   [card-id]
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-public-sharing-enabled)
   (api/check-not-archived (api/read-check Card card-id))
   {:uuid (or (db/select-one-field :public_uuid Card :id card-id)
@@ -710,7 +710,7 @@
 (api/defendpoint DELETE "/:card-id/public_link"
   "Delete the publicly-accessible link to this Card."
   [card-id]
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-public-sharing-enabled)
   (api/check-exists? Card :id card-id, :public_uuid [:not= nil])
   (db/update! Card card-id
@@ -721,7 +721,7 @@
 (api/defendpoint GET "/public"
   "Fetch a list of Cards with public UUIDs. These cards are publicly-accessible *if* public sharing is enabled."
   []
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-public-sharing-enabled)
   (db/select [Card :name :id :public_uuid], :public_uuid [:not= nil], :archived false))
 
@@ -729,7 +729,7 @@
   "Fetch a list of Cards where `enable_embedding` is `true`. The cards can be embedded using the embedding endpoints
   and a signed JWT."
   []
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-embedding-enabled)
   (db/select [Card :name :id], :enable_embedding true, :archived false))
 
diff --git a/src/metabase/api/common/validation.clj b/src/metabase/api/common/validation.clj
index 7a15cbdf509..bb83ec22ed7 100644
--- a/src/metabase/api/common/validation.clj
+++ b/src/metabase/api/common/validation.clj
@@ -30,8 +30,8 @@
    (check-has-general-permission perm-type true))
 
   ([perm-type require-superuser?]
-   (if (and (premium-features/enable-advanced-permissions?)
-            (resolve 'metabase-enterprise.advanced-permissions.common/current-user-has-general-permissions?))
-     (api/check-403 ((resolve 'metabase-enterprise.advanced-permissions.common/current-user-has-general-permissions?) perm-type))
+   (if-let [f (and (premium-features/enable-advanced-permissions?)
+                   (resolve 'metabase-enterprise.advanced-permissions.common/current-user-has-general-permissions?))]
+     (api/check-403 (f perm-type))
      (when require-superuser?
        (api/check-superuser)))))
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index 01bbb2eed11..257775ac621 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -490,7 +490,7 @@
 (api/defendpoint DELETE "/:dashboard-id/public_link"
   "Delete the publicly-accessible link to this Dashboard."
   [dashboard-id]
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-public-sharing-enabled)
   (api/check-exists? Dashboard :id dashboard-id, :public_uuid [:not= nil], :archived false)
   (db/update! Dashboard dashboard-id
@@ -502,7 +502,7 @@
   "Fetch a list of Dashboards with public UUIDs. These dashboards are publicly-accessible *if* public sharing is
   enabled."
   []
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-public-sharing-enabled)
   (db/select [Dashboard :name :id :public_uuid], :public_uuid [:not= nil], :archived false))
 
@@ -510,7 +510,7 @@
   "Fetch a list of Dashboards where `enable_embedding` is `true`. The dashboards can be embedded using the embedding
   endpoints and a signed JWT."
   []
-  (api/check-superuser)
+  (validation/check-has-general-permission :setting)
   (validation/check-embedding-enabled)
   (db/select [Dashboard :name :id], :enable_embedding true, :archived false))
 
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 5b49d236dff..c0215804bf8 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -1827,6 +1827,10 @@
   (testing "DELETE /api/card/:id/public_link"
     (mt/with-temporary-setting-values [enable-public-sharing true]
       (mt/with-temp Card [card (shared-card)]
+        (testing "requires superuser"
+          (is (= "You don't have permissions to do that."
+                 (mt/user-http-request :rasta :delete 403 (format "card/%d/public_link" (u/the-id card))))))
+
         (mt/user-http-request :crowberto :delete 204 (format "card/%d/public_link" (u/the-id card)))
         (is (= false
                (db/exists? Card :id (u/the-id card), :public_uuid (:public_uuid card))))))))
@@ -1852,9 +1856,14 @@
   (testing "GET /api/card/public"
     (mt/with-temporary-setting-values [enable-public-sharing true]
       (mt/with-temp Card [card (shared-card)]
-        (is (= [{:name true, :id true, :public_uuid true}]
-               (for [card (mt/user-http-request :crowberto :get 200 "card/public")]
-                 (m/map-vals boolean (select-keys card [:name :id :public_uuid])))))))))
+        (testing "Test that it requires superuser"
+          (is (= "You don't have permissions to do that."
+                 (mt/user-http-request :rasta :get 403 "card/public"))))
+
+        (testing "Test that superusers can fetch a list of publicly-accessible cards"
+          (is (= [{:name true, :id true, :public_uuid true}]
+                 (for [card (mt/user-http-request :crowberto :get 200 "card/public")]
+                   (m/map-vals boolean (select-keys card [:name :id :public_uuid]))))))))))
 
 (deftest test-that-we-can-fetch-a-list-of-embeddable-cards
   (testing "GET /api/card/embeddable"
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index d7949f8b987..a5528c9808a 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -1172,9 +1172,12 @@
 ;;
 (deftest fetch-public-dashboards-test
   (testing "GET /api/dashboard/public"
-    (testing "Test that we can fetch a list of publicly-accessible dashboards"
-      (mt/with-temporary-setting-values [enable-public-sharing true]
-        (mt/with-temp Dashboard [dashboard (shared-dashboard)]
+    (mt/with-temporary-setting-values [enable-public-sharing true]
+      (mt/with-temp Dashboard [_dashboard (shared-dashboard)]
+        (testing "Test that it requires superuser"
+          (is (= "You don't have permissions to do that."
+                 (mt/user-http-request :rasta :get 403 "dashboard/public"))))
+        (testing "Test that superusers can fetch a list of publicly-accessible dashboards"
           (is (= [{:name true, :id true, :public_uuid true}]
                  (for [dash (mt/user-http-request :crowberto :get 200 "dashboard/public")]
                    (m/map-vals boolean (select-keys dash [:name :id :public_uuid]))))))))))
-- 
GitLab