diff --git a/backend/src/app/services/queries/files.clj b/backend/src/app/services/queries/files.clj index 2144a8192..40d62e95e 100644 --- a/backend/src/app/services/queries/files.clj +++ b/backend/src/app/services/queries/files.clj @@ -273,20 +273,23 @@ and m.deleted_at is null) as graphics_count from file as f left join page as pg on (f.id = pg.file_id) - where is_shared = true + inner join project as p on (p.id = f.project_id) + where f.is_shared = true and f.deleted_at is null and pg.deleted_at is null + and p.deleted_at is null + and p.team_id = ? window pages_w as (partition by f.id order by pg.ordering range between unbounded preceding and unbounded following) order by f.modified_at desc") (s/def ::shared-files - (s/keys :req-un [::profile-id])) + (s/keys :req-un [::profile-id ::team-id])) (sq/defquery ::shared-files - [{:keys [profile-id] :as params}] - (->> (db/exec! db/pool [sql:shared-files]) + [{:keys [profile-id team-id] :as params}] + (->> (db/exec! db/pool [sql:shared-files team-id]) (mapv decode-row))) diff --git a/backend/src/app/services/queries/projects.clj b/backend/src/app/services/queries/projects.clj index 68443b777..f023a8f3f 100644 --- a/backend/src/app/services/queries/projects.clj +++ b/backend/src/app/services/queries/projects.clj @@ -2,91 +2,96 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) 2019 Andrey Antukh +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL (ns app.services.queries.projects (:require [clojure.spec.alpha :as s] - [promesa.core :as p] [app.common.spec :as us] + [app.common.exceptions :as ex] [app.db :as db] [app.services.queries :as sq] - [app.util.blob :as blob])) + [app.services.queries.teams :as teams])) -(declare decode-row) +;; --- Check Project Permissions + +;; This SQL checks if the: (1) project is part of the team where the +;; profile has edition permissions or (2) the profile has direct +;; edition access granted to this project. + +(def sql:project-permissions + "select tp.can_edit, + tp.is_admin, + tp.is_owner + from team_profile_rel as tp + where tp.profile_id = ? + and tp.team_id = ? + union + select pp.can_edit, + pp.is_admin, + pp.is_owner + from project_profile_rel as pp + where pp.profile_id = ? + and pp.project_id = ?;") + +(defn check-edition-permissions! + [conn profile-id project] + (let [rows (db/exec! conn [sql:project-permissions + profile-id + (:team-id project) + profile-id + (:id project)])] + (when (empty? rows) + (ex/raise :type :not-found)) + + (when-not (or (some :can-edit rows) + (some :is-admin rows) + (some :is-owner rows)) + (ex/raise :type :validation + :code :not-authorized)))) -;; TODO: this module should be refactored for to separate the -;; permissions checks from the main queries in the same way as pages -;; and files. This refactor will make this functions more "reusable" -;; and will prevent duplicating queries on `queries.view` ns as -;; example. ;; --- Query: Projects -(def ^:private sql:projects - "with projects as ( - select p.*, - (select count(*) from file as f - where f.project_id = p.id - and deleted_at is null) as file_count - from project as p - inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) - where tpr.profile_id = ? - and p.deleted_at is null - and (tpr.is_admin = true or - tpr.is_owner = true or - tpr.can_edit = true) - union - select p.*, - (select count(*) from file as f - where f.project_id = p.id - and deleted_at is null) - from project as p - inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = ? - and p.deleted_at is null - and (ppr.is_admin = true or - ppr.is_owner = true or - ppr.can_edit = true) - ) - select * - from projects - where team_id = ? - order by modified_at desc") - -(def ^:private sql:project-by-id - "select p.* - from project as p - inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = ? - and p.id = ? - and p.deleted_at is null - and (ppr.is_admin = true or - ppr.is_owner = true or - ppr.can_edit = true)") +(declare retrieve-projects) (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) -(s/def ::project-id ::us/uuid) -(s/def ::projects-by-team +(s/def ::projects (s/keys :req-un [::profile-id ::team-id])) +(sq/defquery ::projects + [{:keys [profile-id team-id]}] + (with-open [conn (db/open)] + (teams/check-read-permissions! conn profile-id team-id) + (retrieve-projects conn team-id))) + +(def sql:projects + "select p.*, + (select count(*) from file as f + where f.project_id = p.id + and deleted_at is null) + from project as p + where team_id = ? + order by modified_at desc") + +(defn retrieve-projects + [conn team-id] + (db/exec! conn [sql:projects team-id])) + +;; --- Query: Projec by ID + +(s/def ::project-id ::us/uuid) (s/def ::project-by-id (s/keys :req-un [::profile-id ::project-id])) -(defn retrieve-projects - [conn profile-id team-id] - (db/exec! conn [sql:projects profile-id profile-id team-id])) - -(defn retrieve-project - [conn profile-id id] - (db/exec-one! conn [sql:project-by-id profile-id id])) - -(sq/defquery ::projects-by-team - [{:keys [profile-id team-id]}] - (retrieve-projects db/pool profile-id team-id)) - (sq/defquery ::project-by-id [{:keys [profile-id project-id]}] - (retrieve-project db/pool profile-id project-id)) + (with-open [conn (db/open)] + (let [project (db/get-by-id conn :project project-id)] + (check-edition-permissions! conn profile-id project) + project))) diff --git a/backend/src/app/services/queries/recent_files.clj b/backend/src/app/services/queries/recent_files.clj index aec3a67c2..c34604685 100644 --- a/backend/src/app/services/queries/recent_files.clj +++ b/backend/src/app/services/queries/recent_files.clj @@ -14,24 +14,20 @@ [app.db :as db] [app.common.spec :as us] [app.services.queries :as sq] - [app.services.queries.projects :refer [retrieve-projects]] + [app.services.queries.teams :as teams] + [app.services.queries.projects :as projects :refer [retrieve-projects]] [app.services.queries.files :refer [decode-row]])) -(def ^:private sql:project-files-recent +(def sql:project-recent-files "select distinct f.*, array_agg(pg.id) over pages_w as pages, first_value(pg.data) over pages_w as data from file as f - inner join file_profile_rel as fp_r on (fp_r.file_id = f.id) left join page as pg on (f.id = pg.file_id) - where fp_r.profile_id = ? - and f.project_id = ? + where f.project_id = ? and f.deleted_at is null and pg.deleted_at is null - and (fp_r.is_admin = true or - fp_r.is_owner = true or - fp_r.can_edit = true) window pages_w as (partition by f.id order by pg.ordering range between unbounded preceding and unbounded following) @@ -39,10 +35,11 @@ limit 5") (defn recent-by-project - [profile-id project] + [conn profile-id project] (let [project-id (:id project)] - (->> (db/exec! db/pool [sql:project-files-recent profile-id project-id]) - (mapv decode-row)))) + (projects/check-edition-permissions! conn profile-id project) + (->> (db/exec! conn [sql:project-recent-files project-id]) + (map decode-row)))) (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) @@ -52,9 +49,11 @@ (sq/defquery ::recent-files [{:keys [profile-id team-id]}] - (->> (retrieve-projects db/pool profile-id team-id) - ;; Retrieve for each proyect the 5 more recent files - (map (partial recent-by-project profile-id)) - ;; Change the structure so it's a map with project-id as keys - (flatten) - (group-by :project-id))) + (with-open [conn (db/open)] + (teams/check-read-permissions! conn profile-id team-id) + (->> (retrieve-projects conn team-id) + ;; Retrieve for each proyect the 5 more recent files + (map (partial recent-by-project conn profile-id)) + ;; Change the structure so it's a map with project-id as keys + (flatten) + (group-by :project-id)))) diff --git a/backend/src/app/services/queries/teams.clj b/backend/src/app/services/queries/teams.clj index 3c5dad0da..4d86477e1 100644 --- a/backend/src/app/services/queries/teams.clj +++ b/backend/src/app/services/queries/teams.clj @@ -30,8 +30,7 @@ (defn check-edition-permissions! [conn profile-id team-id] (let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])] - (when-not (or (= team-id uuid/zero) ;; We can write global-project owned items - (:can-edit row) + (when-not (or (:can-edit row) (:is-admin row) (:is-owner row)) (ex/raise :type :validation @@ -40,9 +39,7 @@ (defn check-read-permissions! [conn profile-id team-id] (let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])] - (when-not (or (= team-id uuid/zero) ;; We can read global-project owned items - (:can-edit row) - (:is-admin row) - (:is-owner row)) + ;; when row is found this means that read permission is granted. + (when-not row (ex/raise :type :validation :code :not-authorized))))