diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj index 9333800af6..81161fa45f 100644 --- a/backend/src/app/rpc/commands/viewer.clj +++ b/backend/src/app/rpc/commands/viewer.clj @@ -56,7 +56,7 @@ (assoc :can-read true))) (defn- get-view-only-bundle - [{:keys [::db/conn] :as cfg} {:keys [profile-id file-id ::perms] :as params}] + [{:keys [::db/conn] :as cfg} {:keys [profile-id file-id share-id ::perms] :as params}] (let [file (bfc/get-file cfg file-id) project (db/get conn :project @@ -89,16 +89,18 @@ (mapv (fn [{:keys [id] :as lib}] (merge lib (bfc/get-file cfg id))))) - links (->> (db/query conn :share-link {:file-id file-id}) - (mapv (fn [row] - (-> row - (update :pages db/decode-pgarray #{}) - ;; NOTE: the flags are deprecated but are still present - ;; on the table on old rows. The flags are pgarray and - ;; for avoid decoding it (because they are no longer used - ;; on frontend) we just dissoc the column attribute from - ;; row. - (dissoc :flags))))) + links (cond->> (->> (db/query conn :share-link {:file-id file-id}) + (mapv (fn [row] + (-> row + (update :pages db/decode-pgarray #{}) + ;; NOTE: the flags are deprecated but are still present + ;; on the table on old rows. The flags are pgarray and + ;; for avoid decoding it (because they are no longer used + ;; on frontend) we just dissoc the column attribute from + ;; row. + (dissoc :flags))))) + (= :share-link (:type perms)) + (filterv #(= (:id %) share-id))) fonts (db/query conn :team-font-variant {:team-id (:id team) diff --git a/backend/test/backend_tests/rpc_viewer_test.clj b/backend/test/backend_tests/rpc_viewer_test.clj index 14040aeacb..8b4b2d2511 100644 --- a/backend/test/backend_tests/rpc_viewer_test.clj +++ b/backend/test/backend_tests/rpc_viewer_test.clj @@ -128,3 +128,64 @@ (let [result (:result out)] (t/is (contains? result :file)) (t/is (contains? result :project))))))) + +(t/deftest share-link-token-disclosure + (let [owner (th/create-profile* 1 {:is-active true}) + proj-id (:default-project-id owner) + + file (th/create-file* 1 {:profile-id (:id owner) + :project-id proj-id + :is-shared false}) + + page-a (get-in file [:data :pages 0]) + page-b (uuid/random) + + ;; Add a second page to the file + _ (th/command! {::th/type :update-file + ::rpc/profile-id (:id owner) + :id (:id file) + :session-id (uuid/random) + :revn 0 + :vern 0 + :changes [{:type :add-page + :id page-b + :page {:id page-b + :name "Page B" + :options {} + :objects {}}}]}) + + ;; Create Link A: restrictive (no pages, team-only comments/inspect) + link-a (th/command! {::th/type :create-share-link + ::rpc/profile-id (:id owner) + :file-id (:id file) + :pages #{} + :who-comment "team" + :who-inspect "team"}) + link-a-id (get-in link-a [:result :id]) + + ;; Create Link B: permissive (all pages, all can comment/inspect) + link-b (th/command! {::th/type :create-share-link + ::rpc/profile-id (:id owner) + :file-id (:id file) + :pages #{page-a page-b} + :who-comment "all" + :who-inspect "all"}) + link-b-id (get-in link-b [:result :id])] + + (t/testing "restrictive share-link holder cannot see other share-link tokens" + (let [out (th/command! {::th/type :get-view-only-bundle + :share-id link-a-id + :file-id (:id file)}) + err (:error out) + result (:result out) + share-links (:share-links result)] + + ;; Should not error + (t/is (nil? err)) + + ;; Should only see the share-link used for authentication + (t/is (= 1 (count share-links))) + (t/is (= link-a-id (:id (first share-links)))) + + ;; Should NOT see Link B's token + (t/is (not (some #(= link-b-id (:id %)) share-links)))))))