♻️ Add admin facilities on the code base

- Fix bugs related to orphan teams on profile deletion
- Separate session based profile-id param from api user provided
This commit is contained in:
Andrey Antukh 2022-12-21 11:53:56 +01:00
parent 53d9b547c3
commit b929564fa7
53 changed files with 872 additions and 468 deletions

View file

@ -15,6 +15,7 @@
[app.db :as db]
[app.http :as-alias http]
[app.loggers.audit :as audit]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
@ -41,7 +42,7 @@
:profile-id :ip-addr :props :context])
(defn- handle-events
[{:keys [::db/pool]} {:keys [profile-id events ::http/request] :as params}]
[{:keys [::db/pool]} {:keys [::rpc/profile-id events ::http/request] :as params}]
(let [ip-addr (audit/parse-client-ip request)
xform (comp
(map #(assoc % :profile-id profile-id))
@ -53,7 +54,6 @@
(when (seq events)
(db/insert-multi! pool :audit-log event-columns events))))
(s/def ::profile-id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::type ::us/string)
(s/def ::props (s/map-of ::us/keyword any?))
@ -67,7 +67,8 @@
(s/def ::events (s/every ::event))
(s/def ::push-audit-events
(s/keys :req-un [::events ::profile-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::events]))
(sv/defmethod ::push-audit-events
{::climit/queue :push-audit-events

View file

@ -6,6 +6,7 @@
(ns app.rpc.commands.auth
(:require
[app.auth :as auth]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
@ -15,6 +16,8 @@
[app.emails :as eml]
[app.http.session :as session]
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.rpc :as-alias rpc]
[app.rpc.climit :as climit]
[app.rpc.commands.teams :as teams]
[app.rpc.doc :as-alias doc]
@ -23,7 +26,6 @@
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.hashers :as hashers]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
@ -31,7 +33,6 @@
(s/def ::fullname ::us/not-empty-string)
(s/def ::lang ::us/string)
(s/def ::path ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::password ::us/not-empty-string)
(s/def ::old-password ::us/not-empty-string)
(s/def ::theme ::us/string)
@ -40,22 +41,6 @@
;; ---- HELPERS
(defn derive-password
[password]
(hashers/derive password
{:alg :argon2id
:memory 16384
:iterations 20
:parallelism 2}))
(defn verify-password
[attempt password]
(try
(hashers/verify attempt password)
(catch Exception _e
{:update false
:valid false})))
(defn email-domain-in-whitelist?
"Returns true if email's domain is in the given whitelist or if
given whitelist is an empty string."
@ -84,7 +69,7 @@
;; ---- COMMAND: login with password
(defn login-with-password
[{:keys [pool session sprops] :as cfg} {:keys [email password] :as params}]
[{:keys [::db/pool session] :as cfg} {:keys [email password scope] :as params}]
(when-not (contains? cf/flags :login)
(ex/raise :type :restriction
@ -96,7 +81,7 @@
(ex/raise :type :validation
:code :account-without-password
:hint "the current account does not have password"))
(:valid (verify-password password (:password profile))))
(:valid (auth/verify-password password (:password profile))))
(validate-profile [profile]
(when-not profile
@ -126,27 +111,37 @@
(profile/decode-profile-row))
invitation (when-let [token (:invitation-token params)]
(tokens/verify sprops {:token token :iss :team-invitation}))
(tokens/verify (::main/props cfg) {:token token :iss :team-invitation}))
;; If invitation member-id does not matches the profile-id, we just proceed to ignore the
;; invitation because invitations matches exactly; and user can't login with other email and
;; accept invitation with other email
response (if (and (some? invitation) (= (:id profile) (:member-id invitation)))
{:invitation-token (:invitation-token params)}
profile)]
(update profile :is-admin (fn [admin?]
(or admin?
(let [admins (cf/get :admins)]
(contains? admins (:email profile)))))))]
(when (and (nil? (:default-team-id profile))
(not= scope "admin"))
(ex/raise :type :restriction
:code :admin-only-profile
:hint "can't login with admin-only profile"))
(-> response
(rph/with-transform (session/create-fn session (:id profile)))
(rph/with-meta {::audit/props (audit/profile->props profile)
::audit/profile-id (:id profile)}))))))
(s/def ::scope ::us/string)
(s/def ::login-with-password
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
:opt-un [::invitation-token ::scope]))
(sv/defmethod ::login-with-password
"Performs authentication using penpot password."
{:auth false
{::rpc/auth false
::climit/queue :auth
::doc/added "1.15"}
[cfg params]
@ -155,11 +150,11 @@
;; ---- COMMAND: Logout
(s/def ::logout
(s/keys :opt-un [::profile-id]))
(s/keys :opt [::rpc/profile-id]))
(sv/defmethod ::logout
"Clears the authentication cookie and logout the current session."
{:auth false
{::rpc/auth false
::doc/added "1.15"}
[{:keys [session] :as cfg} _]
(rph/with-transform {} (session/delete-fn session)))
@ -167,13 +162,13 @@
;; ---- COMMAND: Recover Profile
(defn recover-profile
[{:keys [pool sprops] :as cfg} {:keys [token password]}]
[{:keys [::db/pool] :as cfg} {:keys [token password]}]
(letfn [(validate-token [token]
(let [tdata (tokens/verify sprops {:token token :iss :password-recovery})]
(let [tdata (tokens/verify (::main/props cfg) {:token token :iss :password-recovery})]
(:profile-id tdata)))
(update-password [conn profile-id]
(let [pwd (derive-password password)]
(let [pwd (auth/derive-password password)]
(db/update! conn :profile {:password pwd} {:id profile-id})))]
(db/with-atomic [conn pool]
@ -186,7 +181,7 @@
(s/keys :req-un [::token ::password]))
(sv/defmethod ::recover-profile
{:auth false
{::rpc/auth false
::climit/queue :auth
::doc/added "1.15"}
[cfg params]
@ -195,13 +190,13 @@
;; ---- COMMAND: Prepare Register
(defn validate-register-attempt!
[{:keys [pool sprops]} params]
[{:keys [::db/pool] :as cfg} params]
(when-not (contains? cf/flags :registration)
(if-not (contains? params :invitation-token)
(ex/raise :type :restriction
:code :registration-disabled)
(let [invitation (tokens/verify sprops {:token (:invitation-token params) :iss :team-invitation})]
(let [invitation (tokens/verify (::main/props cfg) {:token (:invitation-token params) :iss :team-invitation})]
(when-not (= (:email params) (:member-email invitation))
(ex/raise :type :restriction
:code :email-does-not-match-invitation
@ -235,7 +230,7 @@
(pos? (compare elapsed register-retry-threshold))))
(defn prepare-register
[{:keys [pool sprops] :as cfg} params]
[{:keys [::db/pool] :as cfg} params]
(validate-register-attempt! cfg params)
@ -264,7 +259,7 @@
params (d/without-nils params)
token (tokens/generate sprops params)]
token (tokens/generate (::main/props cfg) params)]
(with-meta {:token token}
{::audit/profile-id uuid/zero})))
@ -273,7 +268,7 @@
:opt-un [::invitation-token]))
(sv/defmethod ::prepare-register-profile
{:auth false
{::rpc/auth false
::doc/added "1.15"}
[cfg params]
(prepare-register cfg params))
@ -293,7 +288,7 @@
(db/tjson))
password (if-let [password (:password params)]
(derive-password password)
(auth/derive-password password)
"!")
locale (:locale params)
@ -339,15 +334,15 @@
(assoc :default-project-id (:default-project-id team)))))
(defn send-email-verification!
[conn sprops profile]
(let [vtoken (tokens/generate sprops
[conn props profile]
(let [vtoken (tokens/generate props
{:iss :verify-email
:exp (dt/in-future "72h")
:profile-id (:id profile)
:email (:email profile)})
;; NOTE: this token is mainly used for possible complains
;; identification on the sns webhook
ptoken (tokens/generate sprops
ptoken (tokens/generate props
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
@ -360,8 +355,8 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [conn sprops session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify sprops {:token token :iss :prepared-register})
[{:keys [conn session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
is-active (or (:is-active params)
@ -377,7 +372,7 @@
(create-profile-relations conn)
(profile/decode-profile-row)))
invitation (when-let [token (:invitation-token params)]
(tokens/verify sprops {:token token :iss :team-invitation}))]
(tokens/verify (::main/props cfg) {:token token :iss :team-invitation}))]
;; If profile is filled in claims, means it tries to register
;; again, so we proceed to update the modified-at attr
@ -399,7 +394,7 @@
;; email.
(and (some? invitation) (= (:email profile) (:member-email invitation)))
(let [claims (assoc invitation :member-id (:id profile))
token (tokens/generate sprops claims)
token (tokens/generate (::main/props cfg) claims)
resp {:invitation-token token}]
(-> resp
(rph/with-transform (session/create-fn session (:id profile)))
@ -426,7 +421,7 @@
;; In all other cases, send a verification email.
:else
(do
(send-email-verification! conn sprops profile)
(send-email-verification! conn (::main/props cfg) profile)
(rph/with-meta profile
{::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)})))))
@ -435,10 +430,10 @@
(s/keys :req-un [::token ::fullname]))
(sv/defmethod ::register-profile
{:auth false
{::rpc/auth false
::climit/queue :auth
::doc/added "1.15"}
[{:keys [pool] :as cfg} params]
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(register-profile params))))
@ -446,16 +441,16 @@
;; ---- COMMAND: Request Profile Recovery
(defn request-profile-recovery
[{:keys [pool sprops] :as cfg} {:keys [email] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [email] :as params}]
(letfn [(create-recovery-token [{:keys [id] :as profile}]
(let [token (tokens/generate sprops
(let [token (tokens/generate (::main/props cfg)
{:iss :password-recovery
:exp (dt/in-future "15m")
:profile-id id})]
(assoc profile :token token)))
(send-email-notification [conn profile]
(let [ptoken (tokens/generate sprops
(let [ptoken (tokens/generate (::main/props cfg)
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
@ -493,7 +488,7 @@
(s/keys :req-un [::email]))
(sv/defmethod ::request-profile-recovery
{:auth false
{::rpc/auth false
::doc/added "1.15"}
[cfg params]
(request-profile-recovery cfg params))

View file

@ -18,6 +18,7 @@
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.media :as media]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
@ -866,18 +867,17 @@
;; --- Command: export-binfile
(s/def ::file-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::include-libraries? ::us/boolean)
(s/def ::embed-assets? ::us/boolean)
(s/def ::export-binfile
(s/keys :req-un [::profile-id ::file-id ::include-libraries? ::embed-assets?]))
(s/keys :req [::rpc/profile-id] :req-un [::file-id ::include-libraries? ::embed-assets?]))
(sv/defmethod ::export-binfile
"Export a penpot file in a binary format."
{::doc/added "1.15"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id include-libraries? embed-assets?] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id include-libraries? embed-assets?] :as params}]
(files/check-read-permissions! pool profile-id file-id)
(let [body (reify yrs/StreamableResponseBody
(-write-body-to-stream [_ _ output-stream]
@ -892,13 +892,13 @@
(s/def ::file ::media/upload)
(s/def ::import-binfile
(s/keys :req-un [::profile-id ::project-id ::file]))
(s/keys :req [::rpc/profile-id] :req-un [::project-id ::file]))
(sv/defmethod ::import-binfile
"Import a penpot file in a binary format."
{::doc/added "1.15"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id project-id file] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id project-id file] :as params}]
(db/with-atomic [conn pool]
(projects/check-read-permissions! conn profile-id project-id)
(let [ids (import! (assoc cfg

View file

@ -12,6 +12,7 @@
[app.db :as db]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.teams :as teams]
[app.rpc.doc :as-alias doc]
@ -41,7 +42,7 @@
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-comment-threads
(s/and (s/keys :req-un [::profile-id]
(s/and (s/keys :req [::rpc/profile-id]
:opt-un [::file-id ::share-id ::team-id])
#(or (:file-id %) (:team-id %))))
@ -74,7 +75,7 @@
window w as (partition by c.thread_id order by c.created_at asc)")
(defn retrieve-comment-threads
[conn {:keys [profile-id file-id share-id]}]
[conn {:keys [::rpc/profile-id file-id share-id]}]
(files/check-comment-permissions! conn profile-id file-id share-id)
(->> (db/exec! conn [sql:comment-threads profile-id file-id])
(into [] (map decode-row))))
@ -85,11 +86,12 @@
(s/def ::team-id ::us/uuid)
(s/def ::get-unread-comment-threads
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(sv/defmethod ::get-unread-comment-threads
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
(with-open [conn (db/open pool)]
(teams/check-read-permissions! conn profile-id team-id)
(retrieve-unread-comment-threads conn params)))
@ -122,7 +124,7 @@
"select * from threads where count_unread_comments > 0"))
(defn retrieve-unread-comment-threads
[conn {:keys [profile-id team-id]}]
[conn {:keys [::rpc/profile-id team-id]}]
(->> (db/exec! conn [sql:unread-comment-threads-by-team profile-id team-id])
(into [] (map decode-row))))
@ -132,12 +134,13 @@
(s/def ::id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-comment-thread
(s/keys :req-un [::profile-id ::file-id ::id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::id]
:opt-un [::share-id]))
(sv/defmethod ::get-comment-thread
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id id share-id] :as params}]
(with-open [conn (db/open pool)]
(files/check-comment-permissions! conn profile-id file-id share-id)
(let [sql (str "with threads as (" sql:comment-threads ")"
@ -146,7 +149,7 @@
(decode-row)))))
(defn get-comment-thread
[conn {:keys [profile-id file-id id] :as params}]
[conn {:keys [::rpc/profile-id file-id id] :as params}]
(let [sql (str "with threads as (" sql:comment-threads ")"
"select * from threads where id = ?")]
(-> (db/exec-one! conn [sql profile-id file-id id])
@ -160,12 +163,13 @@
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::thread-id ::us/uuid)
(s/def ::get-comments
(s/keys :req-un [::profile-id ::thread-id]
(s/keys :req [::rpc/profile-id]
:req-un [::thread-id]
:opt-un [::share-id]))
(sv/defmethod ::get-comments
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id thread-id share-id] :as params}]
(with-open [conn (db/open pool)]
(let [thread (db/get-by-id conn :comment-thread thread-id)]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id))
@ -191,7 +195,8 @@
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-profiles-for-file-comments
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::share-id]))
(sv/defmethod ::get-profiles-for-file-comments
@ -199,7 +204,7 @@
participants on comment threads of the file."
{::doc/added "1.15"
::doc/changes ["1.15" "Imported from queries and renamed."]}
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id share-id]}]
(with-open [conn (db/open pool)]
(files/check-comment-permissions! conn profile-id file-id share-id)
(get-file-comments-users conn file-id profile-id)))
@ -240,19 +245,19 @@
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::profile-id ::us/uuid)
(s/def ::position ::gpt/point)
(s/def ::content ::us/string)
(s/def ::frame-id ::us/uuid)
(s/def ::create-comment-thread
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::position ::content ::page-id ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::create-comment-thread
{::doc/added "1.15"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id share-id] :as params}]
(db/with-atomic [conn pool]
(files/check-comment-permissions! conn profile-id file-id share-id)
@ -268,7 +273,7 @@
(:next-seqn res)))
(defn create-comment-thread
[conn {:keys [profile-id file-id page-id position content frame-id] :as params}]
[conn {:keys [::rpc/profile-id file-id page-id position content frame-id] :as params}]
(let [seqn (retrieve-next-seqn conn file-id)
now (dt/now)
pname (retrieve-page-name conn params)
@ -316,12 +321,13 @@
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::update-comment-thread-status
(s/keys :req-un [::profile-id ::id]
(s/keys :req [::rpc/profile-id]
:req-un [::id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-status
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id share-id] :as params}]
(db/with-atomic [conn pool]
(let [cthr (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not cthr
@ -346,12 +352,13 @@
(s/def ::is-resolved ::us/boolean)
(s/def ::update-comment-thread
(s/keys :req-un [::profile-id ::id ::is-resolved]
(s/keys :req [::rpc/profile-id]
:req-un [::id ::is-resolved]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id is-resolved share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not thread
@ -370,7 +377,8 @@
(declare create-comment)
(s/def ::create-comment
(s/keys :req-un [::profile-id ::thread-id ::content]
(s/keys :req [::rpc/profile-id]
:req-un [::thread-id ::content]
:opt-un [::share-id]))
(sv/defmethod ::create-comment
@ -381,7 +389,7 @@
(create-comment conn params)))
(defn create-comment
[conn {:keys [profile-id thread-id content share-id] :as params}]
[conn {:keys [::rpc/profile-id thread-id content share-id] :as params}]
(let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true})
(decode-row))
pname (retrieve-page-name conn thread)]
@ -437,7 +445,8 @@
(declare update-comment)
(s/def ::update-comment
(s/keys :req-un [::profile-id ::id ::content]
(s/keys :req [::rpc/profile-id]
:req-un [::id ::content]
:opt-un [::share-id]))
(sv/defmethod ::update-comment
@ -447,7 +456,7 @@
(update-comment conn params)))
(defn update-comment
[conn {:keys [profile-id id content share-id] :as params}]
[conn {:keys [::rpc/profile-id id content share-id] :as params}]
(let [comment (db/get-by-id conn :comment id {:for-update true})
_ (when-not comment (ex/raise :type :not-found))
thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true})
@ -476,11 +485,12 @@
;; --- COMMAND: Delete Comment Thread
(s/def ::delete-comment-thread
(s/keys :req-un [::profile-id ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
(sv/defmethod ::delete-comment-thread
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not (= (:owner-id thread) profile-id)
@ -493,11 +503,12 @@
;; --- COMMAND: Delete comment
(s/def ::delete-comment
(s/keys :req-un [::profile-id ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
(sv/defmethod ::delete-comment
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [comment (db/get-by-id conn :comment id {:for-update true})]
(when-not (= (:owner-id comment) profile-id)
@ -509,12 +520,13 @@
;; --- COMMAND: Update comment thread position
(s/def ::update-comment-thread-position
(s/keys :req-un [::profile-id ::id ::position ::frame-id]
(s/keys :req [::rpc/profile-id]
:req-un [::id ::position ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-position
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id position frame-id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id position frame-id share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
@ -528,12 +540,13 @@
;; --- COMMAND: Update comment frame
(s/def ::update-comment-thread-frame
(s/keys :req-un [::profile-id ::id ::frame-id]
(s/keys :req [::rpc/profile-id]
:req-un [::id ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-frame
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id frame-id share-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id frame-id share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)

View file

@ -12,6 +12,7 @@
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc :as-alias rpc]
[app.rpc.commands.auth :as cmd.auth]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
@ -26,7 +27,7 @@
"A command that is responsible of creating a demo purpose
profile. It only works if the `demo-users` flag is enabled in the
configuration."
{:auth false
{::rpc/auth false
::doc/added "1.15"
::doc/changes ["1.15" "This method is migrated from mutations to commands."]}
[{:keys [pool] :as cfg} _]

View file

@ -19,6 +19,7 @@
[app.db.sql :as sql]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.rpc :as-alias rpc]
[app.rpc.commands.files.thumbnails :as-alias thumbs]
[app.rpc.commands.teams :as teams]
[app.rpc.cond :as-alias cond]
@ -52,7 +53,6 @@
(s/def ::id ::us/uuid)
(s/def ::is-shared ::us/boolean)
(s/def ::name ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::search-term ::us/string)
(s/def ::team-id ::us/uuid)
@ -257,7 +257,8 @@
(str (dt/format-instant modified-at :iso) "-" revn))
(s/def ::get-file
(s/keys :req-un [::profile-id ::id]
(s/keys :req [::rpc/profile-id]
:req-un [::id]
:opt-un [::features]))
(sv/defmethod ::get-file
@ -265,7 +266,7 @@
{::doc/added "1.17"
::cond/get-object #(get-minimal-file %1 (:id %2))
::cond/key-fn get-file-etag}
[{:keys [pool] :as cfg} {:keys [profile-id id features] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id features] :as params}]
(with-open [conn (db/open pool)]
(let [perms (get-permissions conn profile-id id)]
(check-read-permissions! perms)
@ -286,13 +287,14 @@
(s/def ::get-file-fragment
(s/keys :req-un [::file-id ::fragment-id]
:opt-un [::share-id ::profile-id]))
:opt [::rpc/profile-id]
:opt-un [::share-id]))
(sv/defmethod ::get-file-fragment
"Retrieve a file by its ID. Only authenticated users."
{::doc/added "1.17"
:auth false}
[{:keys [pool] :as cfg} {:keys [profile-id file-id fragment-id share-id] :as params}]
::rpc/:auth false}
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id fragment-id share-id] :as params}]
(with-open [conn (db/open pool)]
(let [perms (get-permissions conn profile-id file-id share-id)]
(check-read-permissions! perms)
@ -320,7 +322,7 @@
(d/index-by :object-id :data)))))
(s/def ::get-file-object-thumbnails
(s/keys :req-un [::profile-id ::file-id]))
(s/keys :req [::rpc/profile-id] :req-un [::file-id]))
(sv/defmethod ::get-file-object-thumbnails
"Retrieve a file object thumbnails."
@ -328,7 +330,7 @@
::cond/get-object #(get-minimal-file %1 (:file-id %2))
::cond/reuse-key? true
::cond/key-fn get-file-etag}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(get-object-thumbnails conn file-id)))
@ -350,7 +352,7 @@
order by f.modified_at desc")
(s/def ::get-project-files
(s/keys :req-un [::profile-id ::project-id]))
(s/keys :req [::rpc/profile-id] :req-un [::project-id]))
(defn get-project-files
[conn project-id]
@ -359,7 +361,7 @@
(sv/defmethod ::get-project-files
"Get all files for the specified project."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id project-id] :as params}]
(with-open [conn (db/open pool)]
(projects/check-read-permissions! conn profile-id project-id)
(get-project-files conn project-id)))
@ -370,15 +372,14 @@
(declare get-has-file-libraries)
(s/def ::file-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::has-file-libraries
(s/keys :req-un [::profile-id ::file-id]))
(s/keys :req [::rpc/profile-id] :req-un [::file-id]))
(sv/defmethod ::has-file-libraries
"Checks if the file has libraries. Returns a boolean"
{::doc/added "1.15.1"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(with-open [conn (db/open pool)]
(check-read-permissions! pool profile-id file-id)
(get-has-file-libraries conn params)))
@ -426,7 +427,8 @@
(s/def ::object-id ::us/uuid)
(s/def ::get-page
(s/and
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::page-id ::object-id ::features])
(fn [obj]
(if (contains? obj :object-id)
@ -444,7 +446,7 @@
Mainly used for rendering purposes."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(get-page conn params)))
@ -493,7 +495,7 @@
(into #{} xform (db/exec! conn [sql:team-shared-files team-id]))))
(s/def ::get-team-shared-files
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id] :req-un [::team-id]))
(sv/defmethod ::get-team-shared-files
"Get all file (libraries) for the specified team."
@ -542,13 +544,14 @@
(handle-file-features client-features)))))))
(s/def ::get-file-libraries
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::features]))
(sv/defmethod ::get-file-libraries
"Get libraries used by the specified file."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id features] :as params}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(get-file-libraries conn file-id features)))
@ -569,12 +572,12 @@
(db/exec! conn [sql:library-using-files file-id]))
(s/def ::get-library-file-references
(s/keys :req-un [::profile-id ::file-id]))
(s/keys :req [::rpc/profile-id] :req-un [::file-id]))
(sv/defmethod ::get-library-file-references
"Returns all the file references that use specified file (library) id."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(get-library-file-references conn file-id)))
@ -607,11 +610,12 @@
(db/exec! conn [sql:team-recent-files team-id]))
(s/def ::get-team-recent-files
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(sv/defmethod ::get-team-recent-files
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(teams/check-read-permissions! conn profile-id team-id)
(get-team-recent-files conn team-id)))
@ -639,12 +643,13 @@
(s/def ::revn ::us/integer)
(s/def ::get-file-thumbnail
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::revn]))
(sv/defmethod ::get-file-thumbnail
{::doc/added "1.17"}
[{:keys [pool]} {:keys [profile-id file-id revn]}]
[{:keys [pool]} {:keys [::rpc/profile-id file-id revn]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(-> (get-file-thumbnail conn file-id revn)
@ -730,14 +735,15 @@
(update :objects assoc-thumbnails page-id thumbs)))))
(s/def ::get-file-data-for-thumbnail
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::features]))
(sv/defmethod ::get-file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used
mainly for render thumbnails on dashboard."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as props}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id features] :as props}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(let [file (get-file conn file-id features)]
@ -760,12 +766,13 @@
{:id id}))
(s/def ::rename-file
(s/keys :req-un [::profile-id ::name ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::name ::id]))
(sv/defmethod ::rename-file
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(let [file (rename-file conn params)]
@ -808,12 +815,13 @@
{:id id})))))))))
(s/def ::set-file-shared
(s/keys :req-un [::profile-id ::id ::is-shared]))
(s/keys :req [::rpc/profile-id]
:req-un [::id ::is-shared]))
(sv/defmethod ::set-file-shared
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id is-shared] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(when-not is-shared
@ -836,16 +844,18 @@
{:id id}))
(s/def ::delete-file
(s/keys :req-un [::id ::profile-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
(sv/defmethod ::delete-file
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(absorb-library conn params)
(let [file (mark-file-deleted conn params)]
(rph/with-meta (rph/wrap)
{::audit/props {:project-id (:project-id file)
:name (:name file)
@ -864,12 +874,13 @@
(db/exec-one! conn [sql:link-file-to-library file-id library-id]))
(s/def ::link-file-to-library
(s/keys :req-un [::profile-id ::file-id ::library-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::library-id]))
(sv/defmethod ::link-file-to-library
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id library-id] :as params}]
(when (= file-id library-id)
(ex/raise :type :validation
:code :invalid-library
@ -888,12 +899,13 @@
:library-file-id library-id}))
(s/def ::unlink-file-from-library
(s/keys :req-un [::profile-id ::file-id ::library-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::library-id]))
(sv/defmethod ::unlink-file-from-library
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(unlink-file-from-library conn params)))
@ -909,14 +921,15 @@
:library-file-id library-id}))
(s/def ::update-file-library-sync-status
(s/keys :req-un [::profile-id ::file-id ::library-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::library-id]))
;; TODO: improve naming
(sv/defmethod ::update-file-library-sync-status
"Update the synchronization statos of a file->library link"
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(update-sync conn params)))
@ -931,13 +944,14 @@
{:id file-id}))
(s/def ::ignore-file-library-sync-status
(s/keys :req-un [::profile-id ::file-id ::date]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::date]))
;; TODO: improve naming
(sv/defmethod ::ignore-file-library-sync-status
"Ignore updates in linked files"
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(ignore-sync conn params)))
@ -960,11 +974,13 @@
(s/def ::data (s/nilable ::us/string))
(s/def ::thumbs/object-id ::us/string)
(s/def ::upsert-file-object-thumbnail
(s/keys :req-un [::profile-id ::file-id ::thumbs/object-id ::data]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::thumbs/object-id]
:opt-un [::data]))
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(upsert-file-object-thumbnail! conn params)
@ -987,13 +1003,14 @@
(s/def ::revn ::us/integer)
(s/def ::props map?)
(s/def ::upsert-file-thumbnail
(s/keys :req-un [::profile-id ::file-id ::revn ::data ::props]))
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::revn ::data ::props]))
(sv/defmethod ::upsert-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(upsert-file-thumbnail conn params)

View file

@ -13,6 +13,7 @@
[app.db :as db]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.doc :as-alias doc]
[app.rpc.permissions :as perms]
@ -68,8 +69,8 @@
(files/decode-row file)))
(s/def ::create-file
(s/keys :req-un [::files/profile-id
::files/name
(s/keys :req [::rpc/profile-id]
:req-un [::files/name
::files/project-id]
:opt-un [::files/id
::files/is-shared
@ -78,10 +79,11 @@
(sv/defmethod ::create-file
{::doc/added "1.17"
::webhooks/event? true}
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id project-id] :as params}]
(db/with-atomic [conn pool]
(proj/check-edition-permissions! conn profile-id project-id)
(let [team-id (files/get-team-id conn project-id)]
(let [team-id (files/get-team-id conn project-id)
params (assoc params :profile-id profile-id)]
(-> (create-file conn params)
(vary-meta assoc ::audit/props {:team-id team-id})))))

View file

@ -11,6 +11,7 @@
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.files.create :as files.create]
[app.rpc.commands.files.update :as files.update]
@ -26,8 +27,8 @@
(s/def ::create-page ::us/boolean)
(s/def ::create-temp-file
(s/keys :req-un [::files/profile-id
::files/name
(s/keys :req [::rpc/profile-id]
:req-un [::files/name
::files/project-id]
:opt-un [::files/id
::files/is-shared
@ -36,7 +37,7 @@
(sv/defmethod ::create-temp-file
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id project-id] :as params}]
(db/with-atomic [conn pool]
(proj/check-edition-permissions! conn profile-id project-id)
(files.create/create-file conn (assoc params :deleted-at (dt/in-future {:days 1})))))
@ -44,7 +45,7 @@
;; --- MUTATION COMMAND: update-temp-file
(defn update-temp-file
[conn {:keys [profile-id session-id id revn changes] :as params}]
[conn {:keys [::rpc/profile-id session-id id revn changes] :as params}]
(db/insert! conn :file-change
{:id (uuid/next)
:session-id session-id
@ -95,12 +96,12 @@
nil))
(s/def ::persist-temp-file
(s/keys :req-un [::files/id
::files/profile-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::files/id]))
(sv/defmethod ::persist-temp-file
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id id)
(persist-temp-file conn params)))

View file

@ -20,6 +20,7 @@
[app.loggers.webhooks :as-alias webhooks]
[app.metrics :as mtx]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.commands.files :as files]
[app.rpc.doc :as-alias doc]
@ -52,7 +53,8 @@
(s/def ::revn ::us/integer)
(s/def ::update-file
(s/and
(s/keys :req-un [::files/id ::files/profile-id ::session-id ::revn]
(s/keys :req [::rpc/profile-id]
:req-un [::files/id ::session-id ::revn]
:opt-un [::changes ::changes-with-metadata ::features])
(fn [o]
(or (contains? o :changes)
@ -130,19 +132,20 @@
::webhooks/batch-timeout (dt/duration "2m")
::webhooks/batch-key :id
::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id id)
(db/xact-lock! conn id)
(let [cfg (assoc cfg :conn conn)
params (assoc params :profile-id profile-id)
tpoint (dt/tpoint)]
(-> (update-file cfg params)
(rph/with-defer #(let [elapsed (tpoint)]
(l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))
(defn update-file
[{:keys [conn metrics] :as cfg} {:keys [id profile-id changes changes-with-metadata] :as params}]
[{:keys [conn metrics] :as cfg} {:keys [profile-id id changes changes-with-metadata] :as params}]
(let [file (get-file conn id)
features (->> (concat (:features file)
(:features params))
@ -184,7 +187,7 @@
:team-id (:team-id file)}))))))
(defn- update-file*
[{:keys [conn] :as cfg} {:keys [file changes session-id profile-id] :as params}]
[{:keys [conn] :as cfg} {:keys [profile-id file changes session-id] :as params}]
(when (> (:revn params)
(:revn file))
(ex/raise :type :validation

View file

@ -14,6 +14,7 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.loggers.webhooks :as-alias webhooks]
[app.rpc :as-alias rpc]
[app.rpc.commands.binfile :as binfile]
[app.rpc.commands.files :as files]
[app.rpc.commands.teams :as teams :refer [create-project-role create-project]]
@ -31,14 +32,14 @@
(declare duplicate-file)
(s/def ::id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::duplicate-file
(s/keys :req-un [::profile-id ::file-id]
(s/keys :req [::rpc/profile-id]
:req-un [::file-id]
:opt-un [::name]))
(sv/defmethod ::duplicate-file
@ -47,7 +48,7 @@
::webhooks/event? true}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(duplicate-file conn params)))
(duplicate-file conn (assoc params :profile-id (::rpc/profile-id params)))))
(defn- remap-id
[item index key]
@ -212,7 +213,8 @@
(declare duplicate-project)
(s/def ::duplicate-project
(s/keys :req-un [::profile-id ::project-id]
(s/keys :req [::rpc/profile-id]
:req-un [::project-id]
:opt-un [::name]))
(sv/defmethod ::duplicate-project
@ -221,7 +223,7 @@
::webhooks/event? true}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(duplicate-project conn params)))
(duplicate-project conn (assoc params :profile-id (::rpc/profile-id params)))))
(defn duplicate-project
[conn {:keys [profile-id project-id name] :as params}]
@ -249,9 +251,7 @@
;; create the duplicated project and assign the current profile as
;; a project owner
(create-project conn project)
(create-project-role conn {:project-id (:id project)
:profile-id profile-id
:role :owner})
(create-project-role conn profile-id (:id project) :owner)
;; duplicate all files
(let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files)
@ -322,7 +322,8 @@
(s/def ::ids (s/every ::us/uuid :kind set?))
(s/def ::move-files
(s/keys :req-un [::profile-id ::ids ::project-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::ids ::project-id]))
(sv/defmethod ::move-files
"Move a set of files from one project to other."
@ -330,7 +331,7 @@
::webhooks/event? true}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(move-files conn params)))
(move-files conn (assoc params :profile-id (::rpc/profile-id params)))))
;; --- COMMAND: Move project
@ -362,7 +363,8 @@
(s/def ::move-project
(s/keys :req-un [::profile-id ::team-id ::project-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::project-id]))
(sv/defmethod ::move-project
"Move projects between teams."
@ -370,7 +372,7 @@
::webhooks/event? true}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(move-project conn params)))
(move-project conn (assoc params :profile-id (::rpc/profile-id params)))))
;; --- COMMAND: Clone Template
@ -378,7 +380,8 @@
(s/def ::template-id ::us/not-empty-string)
(s/def ::clone-template
(s/keys :req-un [::profile-id ::project-id ::template-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::project-id ::template-id]))
(sv/defmethod ::clone-template
"Clone into the specified project the template by its id."
@ -387,7 +390,7 @@
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(clone-template params))))
(clone-template (assoc params :profile-id (::rpc/profile-id params))))))
(defn- clone-template
[{:keys [conn templates] :as cfg} {:keys [profile-id template-id project-id]}]

View file

@ -0,0 +1,75 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; 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) KALEIDOS INC
(ns app.rpc.commands.profile
(:require
[app.auth :as auth]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.config :as cf]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
;; --- MUTATION: Set profile password
(declare update-profile-password!)
(s/def ::profile-id ::us/uuid)
(s/def ::password ::us/not-empty-string)
(s/def ::get-derived-password
(s/keys :req [::rpc/profile-id]
:req-un [::password]))
(sv/defmethod ::get-derived-password
"Get derived password, only ADMINS allowed to call this RPC
methods. Designed for administration pannel integration."
{::climit/queue :auth
::climit/key-fn ::rpc/profile-id
::doc/added "1.18"}
[{:keys [::db/pool]} {:keys [password] :as params}]
(db/with-atomic [conn pool]
(let [admins (cf/get :admins)
profile (db/get-by-id conn :profile (::rpc/profile-id params))]
(if (or (:is-admin profile)
(contains? admins (:email profile)))
{:password (auth/derive-password password)}
(ex/raise :type :authentication
:code :only-admins-allowed
:hint "only admins allowed to call this RPC method")))))
;; --- MUTATION: Check profile password
(s/def ::attempt ::us/not-empty-string)
(s/def ::check-profile-password
(s/keys :req [::rpc/profile-id]
:req-un [::profile-id ::password]))
(sv/defmethod ::check-profile-password
"Check profile password, only ADMINS allowed to call this RPC
methods. Designed for administration pannel integration."
{::climit/queue :auth
::climit/key-fn ::rpc/profile-id
::doc/added "1.18"}
[{:keys [::db/pool]} {:keys [profile-id password] :as params}]
(db/with-atomic [conn pool]
(let [admins (cf/get :admins)
profile (db/get-by-id pool :profile (::rpc/profile-id params))]
(if (or (:is-admin profile)
(contains? admins (:email profile)))
(let [profile (if (not= (::rpc/profile-id params) profile-id)
(db/get-by-id conn :profile profile-id)
profile)]
(auth/verify-password password (:password profile)))
(ex/raise :type :authentication
:code :only-admins-allowed
:hint "only admins allowed to call this RPC method")))))

View file

@ -8,6 +8,7 @@
(:require
[app.common.spec :as us]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
@ -47,18 +48,18 @@
order by f.created_at asc")
(defn search-files
[conn {:keys [profile-id team-id search-term] :as params}]
[conn {:keys [::rpc/profile-id team-id search-term] :as params}]
(db/exec! conn [sql:search-files
profile-id team-id
profile-id team-id
search-term]))
(s/def ::profile-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::search-files ::us/string)
(s/def ::search-files
(s/keys :req-un [::profile-id ::team-id]
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]
:opt-un [::search-term]))
(sv/defmethod ::search-files

View file

@ -17,6 +17,7 @@
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.media :as media]
[app.rpc :as-alias rpc]
[app.rpc.climit :as climit]
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
@ -35,7 +36,6 @@
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::team-id ::us/uuid)
@ -78,11 +78,11 @@
(declare retrieve-teams)
(s/def ::get-teams
(s/keys :req-un [::profile-id]))
(s/keys :req [::rpc/profile-id]))
(sv/defmethod ::get-teams
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id]}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(with-open [conn (db/open pool)]
(retrieve-teams conn profile-id)))
@ -122,11 +122,12 @@
(declare retrieve-team)
(s/def ::get-team
(s/keys :req-un [::profile-id ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
(sv/defmethod ::get-team
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id id]}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id]}]
(with-open [conn (db/open pool)]
(retrieve-team conn profile-id id)))
@ -161,11 +162,12 @@
(s/def ::team-id ::us/uuid)
(s/def ::get-team-members
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(sv/defmethod ::get-team-members
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(retrieve-team-members conn team-id)))
@ -177,13 +179,13 @@
(declare retrieve-team-for-file)
(s/def ::get-team-users
(s/and (s/keys :req-un [::profile-id]
(s/and (s/keys :req [::rpc/profile-id]
:opt-un [::team-id ::file-id])
#(or (:team-id %) (:file-id %))))
(sv/defmethod ::get-team-users
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id file-id]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id file-id]}]
(with-open [conn (db/open pool)]
(if team-id
(do
@ -236,11 +238,12 @@
(declare retrieve-team-stats)
(s/def ::get-team-stats
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(sv/defmethod ::get-team-stats
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(retrieve-team-stats conn team-id)))
@ -257,7 +260,8 @@
;; --- Query: Team invitations
(s/def ::get-team-invitations
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(def sql:team-invitations
"select email_to as email, role, (valid_until < now()) as expired
@ -270,7 +274,7 @@
(sv/defmethod ::get-team-invitations
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(get-team-invitations conn team-id)))
@ -285,14 +289,15 @@
(declare ^:private create-team-default-project)
(s/def ::create-team
(s/keys :req-un [::profile-id ::name]
(s/keys :req [::rpc/profile-id]
:req-un [::name]
:opt-un [::id]))
(sv/defmethod ::create-team
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} params]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(create-team conn params)))
(create-team conn (assoc params :profile-id profile-id))))
(defn create-team
"This is a complete team creation process, it creates the team
@ -316,22 +321,20 @@
:is-default is-default})))
(defn- create-team-role
[conn {:keys [team-id profile-id role] :as params}]
[conn {:keys [profile-id team-id role] :as params}]
(let [params {:team-id team-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
(db/insert! conn :team-profile-rel))))
(defn- create-team-default-project
[conn {:keys [team-id profile-id] :as params}]
[conn {:keys [profile-id team-id] :as params}]
(let [project {:id (uuid/next)
:team-id team-id
:name "Drafts"
:is-default true}
project (create-project conn project)]
(create-project-role conn {:project-id (:id project)
:profile-id profile-id
:role :owner})
(create-project-role conn profile-id (:id project) :owner)
project))
;; NOTE: we have project creation here because there are cyclic
@ -351,7 +354,7 @@
:is-default is-default})))
(defn create-project-role
[conn {:keys [project-id profile-id role]}]
[conn profile-id project-id role]
(let [params {:project-id project-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
@ -360,11 +363,12 @@
;; --- Mutation: Update Team
(s/def ::update-team
(s/keys :req-un [::profile-id ::name ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::name ::id]))
(sv/defmethod ::update-team
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [id name profile-id] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id name] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id id)
(db/update! conn :team
@ -379,11 +383,12 @@
(s/def ::reassign-to ::us/uuid)
(s/def ::leave-team
(s/keys :req-un [::profile-id ::id]
(s/keys :req [::rpc/profile-id]
:req-un [::id]
:opt-un [::reassign-to]))
(defn leave-team
[conn {:keys [id profile-id reassign-to]}]
[conn {:keys [::rpc/profile-id id reassign-to]}]
(let [perms (get-permissions conn profile-id id)
members (retrieve-team-members conn id)]
@ -438,7 +443,8 @@
;; --- Mutation: Delete Team
(s/def ::delete-team
(s/keys :req-un [::profile-id ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
;; TODO: right now just don't allow delete default team, in future it
;; should raise a specific exception for signal that this action is
@ -446,7 +452,7 @@
(sv/defmethod ::delete-team
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id id)]
(when-not (:is-owner perms)
@ -477,7 +483,7 @@
:viewer {:is-owner false :is-admin false :can-edit false}))
(defn update-team-member-role
[conn {:keys [team-id profile-id member-id role] :as params}]
[conn {:keys [profile-id team-id member-id role] :as params}]
;; We retrieve all team members instead of query the
;; database for a single member. This is just for
;; convenience, if this becomes a bottleneck or problematic,
@ -524,23 +530,25 @@
nil)))
(s/def ::update-team-member-role
(s/keys :req-un [::profile-id ::team-id ::member-id ::role]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::member-id ::role]))
(sv/defmethod ::update-team-member-role
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(update-team-member-role conn params)))
(update-team-member-role conn (assoc params :profile-id (::rpc/profile-id params)))))
;; --- Mutation: Delete Team Member
(s/def ::delete-team-member
(s/keys :req-un [::profile-id ::team-id ::member-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::member-id]))
(sv/defmethod ::delete-team-member
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id member-id] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id)]
(when-not (or (:is-owner perms)
@ -564,15 +572,16 @@
(s/def ::file ::media/upload)
(s/def ::update-team-photo
(s/keys :req-un [::profile-id ::team-id ::file]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::file]))
(sv/defmethod ::update-team-photo
{::doc/added "1.17"}
[cfg {:keys [file] :as params}]
[cfg {:keys [::rpc/profile-id file] :as params}]
;; Validate incoming mime type
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
(let [cfg (update cfg :storage media/configure-assets-storage)]
(update-team-photo cfg params)))
(update-team-photo cfg (assoc params :profile-id profile-id))))
(defn update-team-photo
[{:keys [pool storage executor] :as cfg} {:keys [profile-id team-id] :as params}]
@ -632,7 +641,7 @@
update set role = ?, updated_at = now();")
(defn- create-invitation-token
[cfg {:keys [valid-until profile-id team-id member-id member-email role]}]
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
(tokens/generate (::main/props cfg)
{:iss :team-invitation
:exp valid-until
@ -715,14 +724,15 @@
(s/def ::email ::us/email)
(s/def ::emails ::us/set-of-valid-emails)
(s/def ::create-team-invitations
(s/keys :req-un [::profile-id ::team-id ::role]
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::role]
:opt-un [::email ::emails]))
(sv/defmethod ::create-team-invitations
"A rpc call that allow to send a single or multiple invitations to
join the team."
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id)
@ -760,7 +770,7 @@
(sv/defmethod ::create-team-with-invitations
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)
@ -791,11 +801,12 @@
;; --- Query: get-team-invitation-token
(s/def ::get-team-invitation-token
(s/keys :req-un [::profile-id ::team-id ::email]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::email]))
(sv/defmethod ::get-team-invitation-token
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email] :as params}]
(check-read-permissions! pool profile-id team-id)
(let [invit (-> (db/get pool :team-invitation
{:team-id team-id
@ -813,11 +824,12 @@
;; --- Mutation: Update invitation role
(s/def ::update-team-invitation-role
(s/keys :req-un [::profile-id ::team-id ::email ::role]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::email ::role]))
(sv/defmethod ::update-team-invitation-role
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id email role] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id)]
@ -833,11 +845,12 @@
;; --- Mutation: Delete invitation
(s/def ::delete-team-invitation
(s/keys :req-un [::profile-id ::team-id ::email]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::email]))
(sv/defmethod ::delete-team-invitation
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id team-id email] :as params}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id email] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id)]

View file

@ -11,6 +11,7 @@
[app.db :as db]
[app.http.session :as session]
[app.loggers.audit :as audit]
[app.rpc :as-alias rpc]
[app.rpc.commands.teams :as teams]
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
@ -27,10 +28,10 @@
(s/def ::verify-token
(s/keys :req-un [::token]
:opt-un [::profile-id]))
:opt [::rpc/profile-id]))
(sv/defmethod ::verify-token
{:auth false
{::rpc/auth false
::doc/added "1.15"}
[{:keys [pool sprops] :as cfg} {:keys [token] :as params}]
(db/with-atomic [conn pool]
@ -126,7 +127,8 @@
:opt-un [::spec.team-invitation/member-id]))
(defmethod process-token :team-invitation
[{:keys [conn session] :as cfg} {:keys [profile-id token]}
[{:keys [conn session] :as cfg}
{:keys [::rpc/profile-id token]}
{:keys [member-id team-id member-email] :as claims}]
(us/verify! ::team-invitation-claims claims)

View file

@ -8,6 +8,7 @@
(:require
[app.common.exceptions :as ex]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.comments :as comments]
[app.rpc.commands.files :as files]
[app.rpc.cond :as-alias cond]
@ -73,16 +74,16 @@
(s/def ::get-view-only-bundle
(s/keys :req-un [::files/file-id]
:opt-un [::files/profile-id
::files/share-id
::files/features]))
:opt-un [::files/share-id
::files/features]
:opt [::rpc/profile-id]))
(sv/defmethod ::get-view-only-bundle
{:auth false
{::rpc/auth false
::cond/get-object #(files/get-minimal-file %1 (:file-id %2))
::cond/key-fn files/get-file-etag
::cond/reuse-key? true
::doc/added "1.17"}
[{:keys [pool]} params]
(with-open [conn (db/open pool)]
(get-view-only-bundle conn params)))
(get-view-only-bundle conn (assoc params :profile-id (::rpc/profile-id params)))))

View file

@ -12,6 +12,7 @@
[app.db :as db]
[app.http.client :as http]
[app.loggers.webhooks :as webhooks]
[app.rpc :as-alias rpc]
[app.rpc.commands.teams :refer [check-edition-permissions! check-read-permissions!]]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
@ -23,7 +24,6 @@
;; --- Mutation: Create Webhook
(s/def ::profile-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::uri ::us/not-empty-string)
(s/def ::is-active ::us/boolean)
@ -33,7 +33,8 @@
"application/transit+json"})
(s/def ::create-webhook
(s/keys :req-un [::profile-id ::team-id ::uri ::mtype]
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::uri ::mtype]
:opt-un [::is-active]))
;; NOTE: for now the quote is hardcoded but this need to be solved in
@ -98,7 +99,7 @@
(sv/defmethod ::create-webhook
{::doc/added "1.17"}
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id] :as params}]
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
(check-edition-permissions! pool profile-id team-id)
(validate-quotes! cfg params)
(->> (validate-webhook! cfg nil params)
@ -109,18 +110,19 @@
(sv/defmethod ::update-webhook
{::doc/added "1.17"}
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [id profile-id] :as params}]
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(let [whook (db/get pool :webhook {:id id})]
(check-edition-permissions! pool profile-id (:team-id whook))
(->> (validate-webhook! cfg whook params)
(p/fmap executor (fn [_] (update-webhook! cfg whook params))))))
(s/def ::delete-webhook
(s/keys :req-un [::profile-id ::id]))
(s/keys :req [::rpc/profile-id]
:req-un [::id]))
(sv/defmethod ::delete-webhook
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id id]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id]}]
(db/with-atomic [conn pool]
(let [whook (db/get conn :webhook {:id id})]
(check-edition-permissions! conn profile-id (:team-id whook))
@ -131,14 +133,15 @@
(s/def ::team-id ::us/uuid)
(s/def ::get-webhooks
(s/keys :req-un [::profile-id ::team-id]))
(s/keys :req [::rpc/profile-id]
:req-un [::team-id]))
(def sql:get-webhooks
"select id, uri, mtype, is_active, error_code, error_count
from webhook where team_id = ? order by uri")
(sv/defmethod ::get-webhooks
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
[{:keys [pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(db/exec! conn [sql:get-webhooks team-id])))