🐛 Fix files, projects and shared-files queries.

This commit is contained in:
Andrey Antukh 2020-08-19 13:04:37 +02:00
parent fc01690315
commit 5ae1b72943
4 changed files with 96 additions and 92 deletions

View file

@ -273,20 +273,23 @@
and m.deleted_at is null) as graphics_count and m.deleted_at is null) as graphics_count
from file as f from file as f
left join page as pg on (f.id = pg.file_id) 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 f.deleted_at is null
and pg.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 window pages_w as (partition by f.id order by pg.ordering
range between unbounded preceding range between unbounded preceding
and unbounded following) and unbounded following)
order by f.modified_at desc") order by f.modified_at desc")
(s/def ::shared-files (s/def ::shared-files
(s/keys :req-un [::profile-id])) (s/keys :req-un [::profile-id ::team-id]))
(sq/defquery ::shared-files (sq/defquery ::shared-files
[{:keys [profile-id] :as params}] [{:keys [profile-id team-id] :as params}]
(->> (db/exec! db/pool [sql:shared-files]) (->> (db/exec! db/pool [sql:shared-files team-id])
(mapv decode-row))) (mapv decode-row)))

View file

@ -2,91 +2,96 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz> ;; 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 (ns app.services.queries.projects
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[promesa.core :as p]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.exceptions :as ex]
[app.db :as db] [app.db :as db]
[app.services.queries :as sq] [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 ;; --- Query: Projects
(def ^:private sql:projects (declare retrieve-projects)
"with projects as (
select p.*, (s/def ::team-id ::us/uuid)
(select count(*) from file as f (s/def ::profile-id ::us/uuid)
where f.project_id = p.id
and deleted_at is null) as file_count (s/def ::projects
from project as p (s/keys :req-un [::profile-id ::team-id]))
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ? (sq/defquery ::projects
and p.deleted_at is null [{:keys [profile-id team-id]}]
and (tpr.is_admin = true or (with-open [conn (db/open)]
tpr.is_owner = true or (teams/check-read-permissions! conn profile-id team-id)
tpr.can_edit = true) (retrieve-projects conn team-id)))
union
select p.*, (def sql:projects
"select p.*,
(select count(*) from file as f (select count(*) from file as f
where f.project_id = p.id where f.project_id = p.id
and deleted_at is null) and deleted_at is null)
from project as p 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 = ? where team_id = ?
order by modified_at desc") order by modified_at desc")
(def ^:private sql:project-by-id (defn retrieve-projects
"select p.* [conn team-id]
from project as p (db/exec! conn [sql:projects team-id]))
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ? ;; --- Query: Projec by 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)")
(s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::project-id ::us/uuid) (s/def ::project-id ::us/uuid)
(s/def ::projects-by-team
(s/keys :req-un [::profile-id ::team-id]))
(s/def ::project-by-id (s/def ::project-by-id
(s/keys :req-un [::profile-id ::project-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 (sq/defquery ::project-by-id
[{:keys [profile-id project-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)))

View file

@ -14,24 +14,20 @@
[app.db :as db] [app.db :as db]
[app.common.spec :as us] [app.common.spec :as us]
[app.services.queries :as sq] [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]])) [app.services.queries.files :refer [decode-row]]))
(def ^:private sql:project-files-recent (def sql:project-recent-files
"select distinct "select distinct
f.*, f.*,
array_agg(pg.id) over pages_w as pages, array_agg(pg.id) over pages_w as pages,
first_value(pg.data) over pages_w as data first_value(pg.data) over pages_w as data
from file as f 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) left join page as pg on (f.id = pg.file_id)
where fp_r.profile_id = ? where f.project_id = ?
and f.project_id = ?
and f.deleted_at is null and f.deleted_at is null
and pg.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 window pages_w as (partition by f.id order by pg.ordering
range between unbounded preceding range between unbounded preceding
and unbounded following) and unbounded following)
@ -39,10 +35,11 @@
limit 5") limit 5")
(defn recent-by-project (defn recent-by-project
[profile-id project] [conn profile-id project]
(let [project-id (:id project)] (let [project-id (:id project)]
(->> (db/exec! db/pool [sql:project-files-recent profile-id project-id]) (projects/check-edition-permissions! conn profile-id project)
(mapv decode-row)))) (->> (db/exec! conn [sql:project-recent-files project-id])
(map decode-row))))
(s/def ::team-id ::us/uuid) (s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid) (s/def ::profile-id ::us/uuid)
@ -52,9 +49,11 @@
(sq/defquery ::recent-files (sq/defquery ::recent-files
[{:keys [profile-id team-id]}] [{:keys [profile-id team-id]}]
(->> (retrieve-projects db/pool profile-id team-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 ;; Retrieve for each proyect the 5 more recent files
(map (partial recent-by-project profile-id)) (map (partial recent-by-project conn profile-id))
;; Change the structure so it's a map with project-id as keys ;; Change the structure so it's a map with project-id as keys
(flatten) (flatten)
(group-by :project-id))) (group-by :project-id))))

View file

@ -30,8 +30,7 @@
(defn check-edition-permissions! (defn check-edition-permissions!
[conn profile-id team-id] [conn profile-id team-id]
(let [row (db/exec-one! conn [sql:team-permissions 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 (when-not (or (:can-edit row)
(:can-edit row)
(:is-admin row) (:is-admin row)
(:is-owner row)) (:is-owner row))
(ex/raise :type :validation (ex/raise :type :validation
@ -40,9 +39,7 @@
(defn check-read-permissions! (defn check-read-permissions!
[conn profile-id team-id] [conn profile-id team-id]
(let [row (db/exec-one! conn [sql:team-permissions 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 ;; when row is found this means that read permission is granted.
(:can-edit row) (when-not row
(:is-admin row)
(:is-owner row))
(ex/raise :type :validation (ex/raise :type :validation
:code :not-authorized)))) :code :not-authorized))))