diff --git a/resources/public/js/permissions.js b/resources/public/js/permissions.js new file mode 100644 index 00000000..0747a5cc --- /dev/null +++ b/resources/public/js/permissions.js @@ -0,0 +1,14 @@ +const showHideNewJarInput = () => { + const tb = $('#scope_to_jar_new'); + if ($('#scope_to_jar_select option:checked').val() === ':new') { + tb.show(); + } else { + tb.hide(); + tb.val(''); + } +}; + +$(() => { + $('#scope_to_jar_select').on('change', showHideNewJarInput); + showHideNewJarInput(); +}); diff --git a/src/clojars/auth.clj b/src/clojars/auth.clj index da60e872..b3ec3fec 100644 --- a/src/clojars/auth.clj +++ b/src/clojars/auth.clj @@ -20,22 +20,18 @@ (defn with-account [f] (friend/authenticated (try-account f))) -(defn authorized-admin? [db account group] +(defn authorized-admin? [db account group scope] (when account - (let [adminnames (db/group-adminnames db group) + (let [adminnames (db/group-adminnames db group scope) allnames (db/group-allnames db group)] (or (some #{account} adminnames) (empty? allnames))))) -(defn authorized-member? [db account group] - (when account - (some #{account} (db/group-membernames db group)))) - (defn authorized-group-access? [db account group] (when account (some #{account} (db/group-allnames db group)))) -(defn require-admin-authorization [db account group f] - (if (authorized-admin? db account group) +(defn require-admin-authorization [db account group scope f] + (if (authorized-admin? db account group scope) (f) (friend/throw-unauthorized friend/*identity* {:cemerick.friend/required-roles group}))) diff --git a/src/clojars/db.clj b/src/clojars/db.clj index 5a3e5b23..62dcf739 100644 --- a/src/clojars/db.clj +++ b/src/clojars/db.clj @@ -92,9 +92,10 @@ "webmaster" "welcome"}) +(def SCOPE-ALL "*") +(def SCOPE-ANY ::scope-any) ;; From https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.894/doc/migration-from-clojure-java-jdbc - (defn do-commands [connectable commands] (if (instance? java.sql.Connection connectable) @@ -228,8 +229,8 @@ {:select :* :from :group_verifications :where [:in :group_name - {:select :name - :from :groups + {:select-distinct :group_name + :from :permissions :where [:and [:= :user username] [:not [:is :inactive true]]]}]})) @@ -243,89 +244,189 @@ (defn find-groupnames [db username] - (mapv :name + (mapv :group_name (q db - {:select :name - :from :groups + {:select-distinct :group_name + :from :permissions :where [:and [:= :user username] [:not [:is :inactive true]]] - :order-by :name}))) + :order-by :group_name}))) + +(defn- with-scoping + [scope where] + {:pre [(some? scope)]} + (cond-> where + (not= SCOPE-ANY scope) (conj [:or + [:= :scope SCOPE-ALL] + [:= :scope scope]]))) (defn group-membernames - [db groupname] + [db groupname scope] (mapv :user (q db - {:select :user - :from :groups - :where [:and - [:= :name groupname] - [:not [:is :inactive true]] - [:not [:is :admin true]]]}))) + {:select-distinct :user + :from :permissions + :where + (with-scoping scope + [:and + [:= :group_name groupname] + [:not [:is :inactive true]] + [:not [:is :admin true]]])}))) (defn group-adminnames - [db groupname] + [db groupname scope] (mapv :user (q db - {:select :user - :from :groups - :where [:and - [:= :name groupname] - [:not [:is :inactive true]] - [:= :admin true]]}))) + {:select-distinct :user + :from :permissions + :where + (with-scoping scope + [:and + [:= :group_name groupname] + [:not [:is :inactive true]] + [:= :admin true]])}))) (defn group-admin-emails - [db groupname] + [db groupname scope] (mapv :email (q db {:select :email :from :users :where [:in :user - {:select :user - :from :groups - :where [:and - [:= :name groupname] - [:not [:is :inactive true]] - [:= :admin true]]}]}))) + {:select-distinct :user + :from :permissions + :where + (with-scoping scope + [:and + [:= :group_name groupname] + [:not [:is :inactive true]] + [:= :admin true]])}]}))) (defn group-activenames [db groupname] (mapv :user (q db - {:select :user - :from :groups + {:select-distinct :user + :from :permissions :where [:and - [:= :name groupname] + [:= :group_name groupname] [:not [:is :inactive true]]]}))) +(defn jar-active-usernames + [db group-name scope] + (into + #{} + (map :user) + (q db + {:select-distinct :user + :from :permissions + :where + (with-scoping scope + [:and + [:= :group_name group-name] + [:not [:is :inactive true]]])}))) + (defn group-active-users [db groupname] (q db {:select :* :from :users :where [:in :user - {:select :user - :from :groups + {:select-distinct :user + :from :permissions :where [:and - [:= :name groupname] + [:= :group_name groupname] [:not [:is :inactive true]]]}]})) (defn group-allnames [db groupname] (mapv :user (q db - {:select :user - :from :groups - :where [:= :name groupname]}))) + {:select-distinct :user + :from :permissions + :where [:= :group_name groupname]}))) + +(defn group-all-actives + [db groupname] + (q db + {:select [:*] + :from :permissions + :where [:and + [:= :group_name groupname] + [:not [:is :inactive true]]] + :order-by [:user :scope]})) + +(defn admin-group-scopes-for-user + [db username groupname] + (into #{} + (map :scope) + (q db + {:select :scope + :from :permissions + :where + [:and + [:= :group_name groupname] + [:= :user username] + [:= :admin true] + [:not [:is :inactive true]]]}))) + +(defn user-has-all-scope? + [db username groupname] + (-> (q db + {:select :user + :from :permissions + :where + [:and + [:= :group_name groupname] + [:= :user username] + [:not [:is :inactive true]] + [:= :scope SCOPE-ALL]] + :limit 1}) + (first) + (some?))) (defn group-actives [db groupname] (q db - {:select [:user :admin] - :from :groups + {:select :* + :from :permissions :where [:and - [:= :name groupname] - [:not [:is :inactive true]]]})) + [:= :group_name groupname] + [:not [:is :inactive true]]] + :order-by [:user :scope]})) + +(defn group-actives-for-user + [db groupname username] + (q db + {:select :* + :from :permissions + :where [:and + [:= :group_name groupname] + [:not [:is :inactive true]] + [:or + ;; user has all scope, so can see all other users + [:= 1 + {:select 1 + :from :permissions + :where + [:and + [:= :group_name groupname] + [:= :user username] + [:not [:is :inactive true]] + [:= :scope SCOPE-ALL]] + :limit 1}] + ;; user has a limited scope, so should only see users within + ;; their scope + [:in :scope + {:select-distinct :scope + :from :permissions + :where + [:and + [:= :group_name groupname] + [:= :user username] + [:not [:is :inactive true]]]}]]] + :order-by [:user :scope]})) (defn jars-by-username [db username] @@ -366,8 +467,8 @@ :join [[{:select [:jar_name [[:max :created] :created]] :from :jars :where [:in :group_name - {:select :name - :from :groups + {:select-distinct :group_name + :from :permissions :where [:and [:= :user username] [:not [:is :inactive true]]]}] @@ -575,9 +676,11 @@ per-page)))) (defn- add-member* - [db groupname username added-by admin?] - (sql/insert! db :groups - {:name groupname + [db groupname scope username added-by admin?] + {:pre [(some? scope)]} + (sql/insert! db :permissions + {:group_name groupname + :scope scope user-column username :added_by added-by :admin (boolean admin?)})) @@ -592,7 +695,7 @@ (sql/insert! db :users (set/rename-keys record {:username user-column})) (doseq [groupname [(str "net.clojars." username) (str "org.clojars." username)]] - (add-member* db groupname username "clojars" true) + (add-member* db groupname SCOPE-ALL username "clojars" true) (verify-group! db username groupname)) record)) @@ -759,49 +862,51 @@ :order-by [[:created :desc]]}))) (defn inactivate-member - [db groupname username inactivated-by] + [db groupname scope username inactivated-by] (execute! db - {:update :groups + {:update :permissions :set {:inactive true :inactivated_by inactivated-by} :where [:and [:= :user username] - [:= :name groupname] + [:= :group_name groupname] + [:= :scope scope] [:not [:is :inactive true]]]})) (defn add-member - [db groupname username added-by] - (inactivate-member db groupname username added-by) - (add-member* db groupname username added-by false)) + [db groupname scope username added-by] + (inactivate-member db groupname scope username added-by) + (add-member* db groupname scope username added-by false)) -(defn add-admin [db groupname username added-by] - (inactivate-member db groupname username added-by) - (add-member* db groupname username added-by true)) +(defn add-admin [db groupname scope username added-by] + (inactivate-member db groupname scope username added-by) + (add-member* db groupname scope username added-by true)) -(defn check-group - "Throws if the group does not exist, is not accessible to the account, or not verified - (when the jarname doesn't already exist). +(defn check-group+project + "Throws if the group does not exist, the account does not have permission to + deploy the project, or the group is not verified (when the jarname doesn't + already exist). We only allow deploys of new jars to verified groups. New versions of existing jars can still be deployed to non-verified groups." [db account groupname jarname] - (let [actives (group-activenames db groupname) + (let [jar-actives (jar-active-usernames db groupname jarname) err (fn [msg] (throw (ex-info msg {:account account :group groupname})))] (cond - ;; group exists, but user doesn't have access to it - (and (seq actives) - (not (some #{account} actives))) - (err (format "You don't have access to the '%s' group" groupname)) + ;; group exists, but user doesn't have rights to deploy to the given project + (and (seq jar-actives) + (not (contains? jar-actives account))) + (err (format "You don't have access to the '%s/%s' project" groupname jarname)) ;; group/jar exists, so new versions can be deployed (jar-exists db groupname jarname) true ;; group doesn't exist, reject since we no longer auto-create groups - (empty? actives) + (empty? jar-actives) (err (format "Group '%s' doesn't exist. See https://bit.ly/3MuKGXO" groupname)) ;; group isn't verified, so new jars/projects can't be deployed to it @@ -814,7 +919,7 @@ [db account groupname] (let [actives (group-activenames db groupname)] (when (empty? actives) - (add-admin db groupname account "clojars")))) + (add-admin db groupname SCOPE-ALL account "clojars")))) (defn- group-names-for-provider [provider provider-username] @@ -842,7 +947,7 @@ (cond (empty? actives) (do - (add-admin db group-name username "clojars") + (add-admin db group-name SCOPE-ALL username "clojars") (verify-group! db username group-name) nil) @@ -873,7 +978,7 @@ (defn add-jar [db account {:keys [group name version description homepage authors packaging licenses scm dependencies]}] - (check-group db account group name) + (check-group+project db account group name) (sql/insert! db :jars {:group_name group :jar_name name @@ -911,7 +1016,7 @@ ;; does not delete jars in the group. should it? (defn delete-group [db group-id] - (sql/delete! db :groups {:name group-id})) + (sql/delete! db :permissions {:group_name group-id})) (defn- find-groups-jars-information [db group-id] diff --git a/src/clojars/db/migrate.clj b/src/clojars/db/migrate.clj index 38134dce..78579b96 100644 --- a/src/clojars/db/migrate.clj +++ b/src/clojars/db/migrate.clj @@ -9,7 +9,7 @@ (java.io File))) -(defn initial-schema [trans] +(defn initial-schema [tx] (doseq [cmd (map str/trim (-> (str "queries" (File/separator) "clojars.sql") io/resource @@ -17,21 +17,21 @@ (str/split #";\s*"))) :when (not (.isEmpty cmd)) :when (not (re-find #"^\s*--" cmd))] - (db/do-commands trans [cmd]))) + (db/do-commands tx [cmd]))) ;; migrations mechanics -(defn run-and-record [migration trans] +(defn run-and-record [migration tx] (println "Running migration:" (:name (meta migration))) - (migration trans) - (sql/insert! trans + (migration tx) + (sql/insert! tx :migrations {:name (-> migration (meta) :name (str)) :created_at (db/get-time)})) (defn- add-deploy-tokens-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "create table deploy_tokens " "(id serial not null primary key," " user_id integer not null references users(id) on delete cascade," @@ -42,35 +42,35 @@ " disabled boolean not null default false)")])) (defn- add-last-used-to-deploy-tokens-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "alter table deploy_tokens " "add last_used timestamp default null")])) (defn- add-group-and-jar-to-deploy-tokens-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "alter table deploy_tokens " "add group_name text default null," "add jar_name text default null")])) (defn- add-mfa-fields-to-users-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "alter table users " "add otp_secret_key text default null," "add otp_recovery_code text default null," "add otp_active boolean default false")])) (defn- add-hash-to-deploy-tokens-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "alter table deploy_tokens " "add token_hash text default null")])) (defn- add-group-verifications-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "create table group_verifications " "(id serial not null primary key," "group_name text unique not null," @@ -78,8 +78,8 @@ "created timestamp not null default current_timestamp)")])) (defn- add-audit-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "create table audit " "(\"user\" text," "group_name text," @@ -90,35 +90,49 @@ "created timestamp not null default current_timestamp)")])) (defn- add-single-use-to-tokens - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx ["create type single_use_status as enum ('no', 'yes', 'used')" "alter table deploy_tokens add single_use single_use_status default 'no'"])) (defn- add-expires-at-to-tokens - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx ["alter table deploy_tokens add expires_at timestamp"])) (defn- add-send-deploy-emails-to-users - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx ["alter table users add send_deploy_emails boolean default false"])) (defn- add-indexes-to-deps-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx ["create index deps_idx0 on deps (dep_group_name, dep_jar_name)" "create index deps_idx1 on deps (group_name, jar_name, version)"])) (defn- add-group-settings-table - [trans] - (db/do-commands trans + [tx] + (db/do-commands tx [(str "create table group_settings " "(group_name text primary key," "require_mfa_to_deploy bool," "updated timestamp not null default current_timestamp)")])) +(defn- rename-groups-to-permissions + [tx] + (db/do-commands + tx + ["alter table groups rename to permissions" + "alter table permissions rename column name to group_name"])) + +(defn- add-scope-to-permissions + [tx] + (db/do-commands + tx + ["alter table permissions add scope text default '*' not null" + "create index permissions_idx0 on permissions (group_name, scope)"])) + (def migrations [#'initial-schema #'add-deploy-tokens-table @@ -132,17 +146,19 @@ #'add-expires-at-to-tokens #'add-send-deploy-emails-to-users #'add-indexes-to-deps-table - #'add-group-settings-table]) + #'add-group-settings-table + #'rename-groups-to-permissions + #'add-scope-to-permissions]) (defn migrate [db] (db/do-commands db [(str "create table if not exists migrations " "(name varchar not null, " "created_at timestamp not null default current_timestamp)")]) - (jdbc/with-transaction [trans db] + (jdbc/with-transaction [tx db] (let [has-run? (into #{} (map :migrations/name) - (sql/query trans ["SELECT name FROM migrations"]))] + (sql/query tx ["SELECT name FROM migrations"]))] (doseq [m migrations :when (not (has-run? (str (:name (meta m)))))] - (run-and-record m trans))))) + (run-and-record m tx))))) diff --git a/src/clojars/dev/setup.clj b/src/clojars/dev/setup.clj index 97dc8122..12ae7689 100644 --- a/src/clojars/dev/setup.clj +++ b/src/clojars/dev/setup.clj @@ -21,7 +21,7 @@ (db/do-commands db ["delete from deps" - "delete from groups" + "delete from permissions" "delete from jars" "delete from users" "delete from group_verifications" @@ -89,7 +89,8 @@ [_ group-path artifact-id] (re-find group-artifact-pattern (get-path parent)) version (.getName version-dir) group-id (str/lower-case (fu/path->group group-path)) - user (or (first (db/group-adminnames db group-id)) (rand-nth users))]] + user (or (first (db/group-adminnames db group-id db/SCOPE-ALL)) + (rand-nth users))]] (when-not (db/find-jar db group-id artifact-id version) (printf "Importing %s/%s %s (user: %s)\n" group-id artifact-id version user) (db/add-group db user group-id) diff --git a/src/clojars/email.clj b/src/clojars/email.clj index 1c264c6c..e53dc6fc 100644 --- a/src/clojars/email.clj +++ b/src/clojars/email.clj @@ -53,12 +53,12 @@ (reset! email-latch (CountDownLatch. n)))) (defn wait-for-mock-emails - "Blocks for up to `wait-ms` (default: 100ms) waiting for `n` emails to be sent + "Blocks for up to `wait-ms` (default: 1000ms) waiting for `n` emails to be sent via the mock, where `n` was passed to `expect-mock-emails` (defaulting to 1 if not called). Returns true if `n` reached within that time. Reset with `expect-mock-emails` between tests using the same system." ([] - (wait-for-mock-emails 100)) + (wait-for-mock-emails 1000)) ([wait-ms] (.await @email-latch wait-ms TimeUnit/MILLISECONDS))) diff --git a/src/clojars/notifications/group.clj b/src/clojars/notifications/group.clj index 5169b9a9..04c0721d 100644 --- a/src/clojars/notifications/group.clj +++ b/src/clojars/notifications/group.clj @@ -3,34 +3,40 @@ [clojars.notifications :as notifications] [clojars.notifications.common :as common])) -(defmethod notifications/notification :group-member-added +(defmethod notifications/notification :group-permission-added [_type mailer {:as _user username :user} - {:as data :keys [admin? group member admin-emails member-email]}] - (let [subject (format "A%s member was added to the group %s" + {:as data :keys [admin? group member admin-emails member-email scope-to-jar]}] + (let [subject (format "A%s permission was added to the group %s" (if admin? "n admin" "") group) body [(format - "User '%s' was added%s to the %s group by %s." + "User '%s' was added%s to the %s group with scope %s by %s." member (if admin? " as an admin" "") group + (if (nil? scope-to-jar) + ":all-jars" + (format "'%s'" scope-to-jar)) username) (common/details-table data)] emails (set (concat admin-emails [member-email]))] (doseq [email emails] (notifications/send mailer email subject body)))) -(defmethod notifications/notification :group-member-removed +(defmethod notifications/notification :group-permission-removed [_type mailer {:as _user username :user} - {:as data :keys [group member admin-emails member-email]}] - (let [subject (format "A member was removed from the group %s" + {:as data :keys [group member admin-emails member-email scope-to-jar]}] + (let [subject (format "A permission was removed from the group %s" group) body [(format - "User '%s' was removed from the %s group by %s." + "User '%s' was removed from the %s group with scope %s by %s." member group + (if (nil? scope-to-jar) + ":all-jars" + (format "'%s'" scope-to-jar)) username) (common/details-table data)] emails (set (concat admin-emails [member-email]))] diff --git a/src/clojars/routes/group.clj b/src/clojars/routes/group.clj index 4a07c3d7..f286c78c 100644 --- a/src/clojars/routes/group.clj +++ b/src/clojars/routes/group.clj @@ -9,70 +9,14 @@ [compojure.core :as compojure :refer [GET POST DELETE]])) (defn- get-members [db groupname] - (let [actives (seq (db/group-actives db groupname))] - (when (seq actives) - (auth/try-account - #(view/show-group db % groupname actives))))) - -(defn- toggle-or-add-member [db event-emitter groupname username make-admin? details] - (let [actives (seq (db/group-actives db groupname)) - membernames (->> actives - (filter (complement view/is-admin?)) - (map :user)) - adminnames (->> actives - (filter view/is-admin?) - (map :user)) - user-to-add (db/find-user db username) - handler-fn (fn [account admin? group-users] - #(log/with-context {:tag :toggle-or-add-group-member - :group groupname - :username account - :username-to-add username - :admin? admin?} - (cond - (= account username) - (do - (log/info {:status :failed - :reason :self-toggle}) - (view/show-group db account groupname actives - "Cannot change your own membership!")) - - (some #{username} group-users) - (do - (log/info {:status :failed - :reason :user-already-member}) - (view/show-group db account groupname actives - (format "They're already an %s!" (if admin? "admin" "member")))) - - user-to-add - (let [add-fn (if admin? db/add-admin db/add-member)] - (add-fn db groupname username account) - (event/emit event-emitter - :group-member-added - (merge - details - {:admin? admin? - :admin-emails (db/group-admin-emails db groupname) - :group groupname - :member username - :member-email (:email user-to-add) - :username account})) - (log/info {:status :success}) - (log/audit db {:tag :member-added - :message (format "user '%s' added as %s" username - (if admin? "admin" "member"))}) - (view/show-group db account groupname - (into (remove (fn [active] - (= username (:user active))) actives) - [{:user username :admin admin?}]))) + (when (seq (db/group-all-actives db groupname)) + (auth/try-account + #(view/show-group db % groupname)))) - :else - (do - (log/info {:status :failed - :reason :user-not-found}) - (view/show-group db account groupname actives - (str "No such user: " - username))))))] +(defn- toggle-or-add-member + [db event-emitter groupname username make-admin? scope-to-jar details] + (let [actives (db/group-all-actives db groupname) + user-to-add (db/find-user db username)] (when (seq actives) (auth/try-account (fn [account] @@ -80,12 +24,91 @@ db account groupname - (handler-fn account - make-admin? - (if make-admin? adminnames membernames)))))))) + scope-to-jar + (fn [] + (log/with-context {:tag :toggle-or-add-group-permission + :group groupname + :username account + :username-to-add username + :make-admin? make-admin?} + (cond + (= account username) + (do + (log/info {:status :failed + :reason :self-toggle}) + (view/show-group db account groupname + "Cannot change your own permission")) + + (some #{[username scope-to-jar make-admin?]} + (mapv (juxt :user :scope :admin) actives)) + (do + (log/info {:status :failed + :reason :user-already-permission}) + (view/show-group + db account groupname + (format "User already in group with %s scope as %s" + scope-to-jar + (if make-admin? "admin" "member")))) -(defn- remove-member [db event-emitter groupname username details] - (let [actives (seq (db/group-actives db groupname))] + (and (not= scope-to-jar db/SCOPE-ALL) + (some #{[username db/SCOPE-ALL]} + (mapv (juxt :user :scope) actives))) + (do + (log/info {:status :failed + :reason :user-already-has-all-scope}) + (view/show-group + db account groupname + (format "User has '%s' scope, so can't be further scoped" + db/SCOPE-ALL))) + + (and (= scope-to-jar db/SCOPE-ALL) + (seq (into [] + (comp + (filter #(= username (:user %))) + (map :scope) + (remove #(= db/SCOPE-ALL %))) + actives))) + (do + (log/info {:status :failed + :reason :user-already-has-project-scope}) + (view/show-group + db account groupname + (format "User has project scope, so can't be given '%s' scope" + db/SCOPE-ALL))) + + user-to-add + (let [add-fn (if make-admin? db/add-admin db/add-member)] + (add-fn db groupname scope-to-jar username account) + (event/emit event-emitter + :group-permission-added + (merge + details + {:admin? make-admin? + :admin-emails (db/group-admin-emails db groupname scope-to-jar) + :group groupname + :scope-to-jar scope-to-jar + :member username + :member-email (:email user-to-add) + :username account})) + (log/info {:status :success}) + (log/audit db {:tag :permission-added + :message (format "user '%s' added as %s with '%s' scope" + username + (if make-admin? "admin" "member") + scope-to-jar)}) + (view/show-group db account groupname)) + + :else + (do + (log/info {:status :failed + :reason :user-not-found}) + (view/show-group db account groupname + (str "No such user: " + username)))))))))))) + +(defn- remove-member + [db event-emitter groupname scope-to-jar username details] + (let [actives (db/jar-active-usernames db groupname scope-to-jar)] (when (seq actives) (auth/try-account (fn [account] @@ -93,47 +116,51 @@ db account groupname - #(log/with-context {:tag :remove-group-member + scope-to-jar + #(log/with-context {:tag :remove-group-permission :username account :group groupname + :scope-to-jar scope-to-jar :username-to-remove username} (cond (= account username) (do (log/info {:status :failed :reason :self-removal}) - (view/show-group db account groupname actives + (view/show-group db account groupname "Cannot remove yourself!")) - (some #{username} (map :user actives)) + (contains? actives username) (do - (db/inactivate-member db groupname username account) + (db/inactivate-member db groupname scope-to-jar username account) (event/emit event-emitter - :group-member-removed + :group-permission-removed (merge details - {:admin-emails (db/group-admin-emails db groupname) + {:admin-emails (db/group-admin-emails db groupname scope-to-jar) :group groupname + :scope-to-jar scope-to-jar :member username :member-email (:email (db/find-user db username)) :username account})) (log/info {:status :success}) - (log/audit db {:tag :member-removed - :message (format "user '%s' removed" username)}) - (view/show-group db account groupname - (remove (fn [active] (= username (:user active))) actives))) + (log/audit db {:tag :permission-removed + :message (format "user '%s' with scope '%s' removed" + username + scope-to-jar)}) + (view/show-group db account groupname)) :else (do (log/info {:status :failed - :reason :member-not-in-group}) - (view/show-group db account groupname actives - (str "No such member: " + :reason :permission-not-in-group}) + (view/show-group db account groupname + (str "No such permission in group: " username))))))))))) (defn- update-group-settings [db event-emitter groupname require-mfa? details] - (let [actives (seq (db/group-actives db groupname)) + (let [actives (seq (db/group-all-actives db groupname)) settings {:require_mfa_to_deploy require-mfa?}] (auth/try-account (fn [account] @@ -141,6 +168,7 @@ db account groupname + db/SCOPE-ALL #(log/with-context {:tag :update-group-settings :username account :group groupname @@ -152,15 +180,18 @@ :group-settings-updated (merge details - {:admin-emails (db/group-admin-emails db groupname) + ;; This email goes to any admin that has any scope + ;; since this change will impact all deploys within + ;; the group + {:admin-emails (db/group-admin-emails db groupname db/SCOPE-ALL) :group groupname :username account :settings settings})) (log/info {:status :success}) (log/audit db {:tag :group-settings-updated :message (format "settings updated: %s" (pr-str settings))}) - (view/show-group db account groupname actives)) - (view/show-group db account groupname actives + (view/show-group db account groupname)) + (view/show-group db account groupname "Cannot set settings for non-existent group")))))))) (defn routes [db event-emitter] @@ -169,7 +200,11 @@ (get-members db groupname)) (POST ["/groups/:groupname/settings", :groupname #"[^/]+"] [groupname require_mfa :as request] (update-group-settings db event-emitter groupname (= "1" require_mfa) (common/request-details request))) - (POST ["/groups/:groupname", :groupname #"[^/]+"] [groupname username admin :as request] - (toggle-or-add-member db event-emitter groupname username (= "1" admin) (common/request-details request))) - (DELETE ["/groups/:groupname", :groupname #"[^/]+"] [groupname username :as request] - (remove-member db event-emitter groupname username (common/request-details request))))) + (POST ["/groups/:groupname", :groupname #"[^/]+"] [admin groupname scope_to_jar scope_to_jar_new username :as request] + (toggle-or-add-member db event-emitter groupname username (= "1" admin) + (if (= ":new" scope_to_jar) + scope_to_jar_new + scope_to_jar) + (common/request-details request))) + (DELETE ["/groups/:groupname", :groupname #"[^/]+"] [groupname scope_to_jar username :as request] + (remove-member db event-emitter groupname scope_to_jar username (common/request-details request))))) diff --git a/src/clojars/routes/repo.clj b/src/clojars/routes/repo.clj index 6c0db4c4..1d02695e 100644 --- a/src/clojars/routes/repo.clj +++ b/src/clojars/routes/repo.clj @@ -143,9 +143,9 @@ (ex-data e-or-message)) cause))) -(defn- check-group [db account group artifact] +(defn- check-group+project [db account group artifact] (try - (db/check-group db account group artifact) + (db/check-group+project db account group artifact) (catch Exception e (throw-forbidden e {:account account @@ -162,7 +162,7 @@ :username account} (when artifact ;; will throw if there are any issues - (check-group db account groupname artifact)) + (check-group+project db account groupname artifact)) (let [upload-dir (find-upload-dir groupname artifact version timestamp-version session)] (f account upload-dir) ;; should we only do 201 if the file didn't already exist? @@ -502,7 +502,7 @@ ;; but since this includes the group authorization check, ;; we do it here just in case. Will throw if there are any ;; issues. - (check-group db account groupname artifact) + (check-group+project db account groupname artifact) (deploy-post-finalized-file storage upload-dir file))))))) ;; web handlers diff --git a/src/clojars/web/dashboard.clj b/src/clojars/web/dashboard.clj index e2591e0d..0a276c0b 100644 --- a/src/clojars/web/dashboard.clj +++ b/src/clojars/web/dashboard.clj @@ -110,5 +110,6 @@ [:li (link-to "https://github.com/clojars/clojars-web/wiki/Pushing" "How do I deploy to clojars?")] [:li (link-to "https://github.com/clojars/clojars-web/wiki/Data" "How can I access clojars data programatically?")] [:li (link-to "https://github.com/clojars/clojars-web/wiki/Groups" "What are groups?")] - [:li (link-to "https://github.com/clojars/clojars-web/wiki/POM" "What does my POM need to look like?")]]]]] + [:li (link-to "https://github.com/clojars/clojars-web/wiki/Verified-Group-Names" "How do I verify a group name?")] + [:li (link-to "https://github.com/clojars/clojars-web/wiki/" "More...")]]]]] (audit-table db account {:username account}))) diff --git a/src/clojars/web/group.clj b/src/clojars/web/group.clj index def02dc8..ea8abdc4 100644 --- a/src/clojars/web/group.clj +++ b/src/clojars/web/group.clj @@ -1,94 +1,152 @@ (ns clojars.web.group (:require - [clojars.auth :refer [authorized-admin? authorized-member?]] - [clojars.db :refer [find-group-verification get-group-settings jars-by-groupname]] + [clojars.db :as db] [clojars.web.common :refer [audit-table form-table html-doc jar-link user-link error-list verified-group-badge-small]] [clojars.web.safe-hiccup :refer [form-to]] [clojars.web.structured-data :as structured-data] [hiccup.element :refer [unordered-list]] - [hiccup.form :refer [text-field hidden-field]])) + [hiccup.form :refer [text-field hidden-field select-options]])) -(def is-admin? :admin) +(defn- scope-options + [db account groupname actives all-group-jars] + (let [admin-scopes-for-user (db/admin-group-scopes-for-user db account groupname) + admin-all? (contains? admin-scopes-for-user db/SCOPE-ALL) + all-visible-jar-scopes (into #{} + (comp + (filter #(or + admin-all? + (contains? admin-scopes-for-user %))) + (remove #(= db/SCOPE-ALL %))) + (concat + (map :jar_name all-group-jars) + (map :scope actives)))] + (into (if admin-all? + [["All Projects" "*"] + ["New Project..." ":new"]] + []) + (map #(vector % %)) + all-visible-jar-scopes))) -(defn show-group [db account groupname actives & errors] - (let [admin? (authorized-admin? db account groupname) - member? (authorized-member? db account groupname) - show-membership-details? (or admin? member?) - verified-group? (find-group-verification db groupname) - group-settings (get-group-settings db groupname)] - (html-doc (str groupname " group") {:account account :description (format "Clojars projects in the %s group" groupname)} - [:div.col-xs-12 - (structured-data/breadcrumbs [{:url (str "https://clojars.org/groups/" groupname) - :name groupname}]) - [:div#group-title - [:h1 (str groupname " group")] - (when (and verified-group? show-membership-details?) - verified-group-badge-small)] - [:h2 "Projects"] - (unordered-list (map jar-link (jars-by-groupname db groupname))) - [:h2 "Members"] - (if show-membership-details? - [:table.group-member-list - [:thead - [:tr - [:th "Username"] - [:th "Admin?"]]] - [:tbody - (for [active (sort-by :user actives)] - [:tr - [:td (user-link (:user active))] - [:td - (if (is-admin? active) - "Yes" - "No")] - (when admin? - (list - [:td - (cond - (= account (:user active)) "" - (is-admin? active) - (form-to [:post (str "/groups/" groupname)] - (hidden-field "username" (:user active)) - (hidden-field "admin" 0) - [:input.button {:type "submit" :value "Toggle Admin"}]) - :else - (form-to [:post (str "/groups/" groupname)] - (hidden-field "username" (:user active)) - (hidden-field "admin" 1) - [:input.button.green-button {:type "submit" :value "Toggle Admin"}]))] - [:td - (if (= account (:user active)) - "" - (form-to [:delete (str "/groups/" groupname)] - (hidden-field "username" (:user active)) - [:input.button.red-button {:type "submit" :value "Remove Member"}]))]))])]] - (unordered-list (map user-link (sort (map :user actives))))) - (error-list errors) - (when admin? - (list - [:div.add-member - [:h2 "Add member to group"] - (form-table - [:post (str "/groups/" groupname)] - [[[:label "Username "] - (text-field "username")] - [[:label "Admin? "] - [:input {:type "checkbox" - :name "admin" - :id "admin" - :value 1 - :checked false}]]] - [:input.button {:type "submit" :value "Add Member"}])] - [:div.group-settings - [:h2 "Group Settings"] - (form-table - [:post (format "/groups/%s/settings" groupname)] - [[[:label "Require users to have two-factor auth enabled to deploy? "] - [:input {:type "checkbox" - :name "require_mfa" - :id "require_mfa" - :value 1 - :checked (:require_mfa_to_deploy group-settings false)}]]] - [:input.button {:type "submit" :value "Update Settings"}])])) - (when show-membership-details? - (audit-table db groupname {:group-name groupname}))]))) +(defn show-group + [db account groupname & errors] + (let [actives (db/group-actives db groupname) + actives-for-user (db/group-actives-for-user db groupname account) + user-admin-scopes (into #{} + (comp + (filter #(= account (:user %))) + (filter :admin) + (map :scope)) + actives-for-user) + current-user-admin? (seq user-admin-scopes) + current-user-admin-for-scope? (fn [scope] + (or (contains? user-admin-scopes db/SCOPE-ALL) + (contains? user-admin-scopes scope))) + show-membership-details? (seq actives-for-user) + verified-group? (db/find-group-verification db groupname) + group-settings (db/get-group-settings db groupname) + all-group-jars (db/jars-by-groupname db groupname)] + (html-doc + (str groupname " group") + {:account account + :description (format "Clojars projects in the %s group" groupname) + :extra-js ["/js/permissions.js"]} + [:div.col-xs-12 + (structured-data/breadcrumbs [{:url (str "https://clojars.org/groups/" groupname) + :name groupname}]) + [:div#group-title + [:h1 (str groupname " group")] + (when (and verified-group? show-membership-details?) + verified-group-badge-small)] + [:h2 "Projects"] + (unordered-list (map jar-link all-group-jars)) + [:h2 "Permissions"] + (if show-membership-details? + (list + [:details.help + [:summary "Help"] + [:p "A user that has a permission in a group can deploy new versions + of projects that are within the group. Users scoped to All + Projects (*) can deploy any existing project within the group, and + can deploy new projects. A user scoped to one or more projects can + only deploy to those projects."] + [:p "An Admin permission for a given project scope will allow the + user (in addition to the right to deploy) to add permissions, but + only within that scope. For example, an Admin user scoped to the + 'foo' project can only add new permissions scoped to 'foo'."]] + [:table.group-member-list + [:thead + [:tr + [:th "Username"] + [:th "Scope"] + [:th "Admin?"]]] + [:tbody + (for [active (sort-by :user actives) + :let [{:keys [admin scope user]} active]] + [:tr + [:td (user-link user)] + [:td scope] + [:td + (if admin "Yes" "No")] + (when (and (not= account user) + (current-user-admin-for-scope? scope)) + (list + [:td + (form-to [:post (str "/groups/" groupname)] + (hidden-field "username" user) + (hidden-field "scope_to_jar" scope) + (hidden-field "admin" (if admin 0 1)) + [:input.button.green-button {:type "submit" + :value "Toggle Admin"}])] + [:td + (form-to [:delete (str "/groups/" groupname)] + (hidden-field "username" user) + (hidden-field "scope_to_jar" scope) + [:input.button.red-button {:type "submit" + :value "Remove Permission"}])]))])]]) + (unordered-list (->> (db/group-all-actives db groupname) + (into [] + (comp + (map :user) + (distinct))) + (sort) + (mapv user-link)))) + (error-list errors) + (when current-user-admin? + (list + [:div.add-member + [:h2 "Add permission to group"] + (form-table + [:post (str "/groups/" groupname)] + [[[:label "Username "] + (text-field "username")] + [[:label "Admin? "] + [:input {:type "checkbox" + :name "admin" + :id "admin" + :value 1 + :checked false}]] + [[:label "Scope to project "] + [:span + [:select + {:name :scope_to_jar + :id :scope_to_jar_select} + (select-options (scope-options db account groupname actives all-group-jars))] + " " + (text-field {:placeholder "new project"} + :scope_to_jar_new)]]] + [:input.button {:type "submit" :value "Add Permission"}])] + ;; Only all-scoped admins can change group-wide settings + (when (current-user-admin-for-scope? db/SCOPE-ALL) + [:div.group-settings + [:h2 "Group Settings"] + (form-table + [:post (format "/groups/%s/settings" groupname)] + [[[:label "Require users to have two-factor auth enabled to deploy? "] + [:input {:type "checkbox" + :name "require_mfa" + :id "require_mfa" + :value 1 + :checked (:require_mfa_to_deploy group-settings false)}]]] + [:input.button {:type "submit" :value "Update Settings"}])]))) + (when show-membership-details? + (audit-table db groupname {:group-name groupname}))]))) diff --git a/test/clojars/integration/group_permissions_test.clj b/test/clojars/integration/group_permissions_test.clj new file mode 100644 index 00000000..ee804d74 --- /dev/null +++ b/test/clojars/integration/group_permissions_test.clj @@ -0,0 +1,556 @@ +(ns clojars.integration.group-permissions-test + (:require + [clojars.db :as db] + [clojars.email :as email] + [clojars.integration.steps :refer [login-as register-as]] + ;; for defmethods + [clojars.notifications.group] + [clojars.test-helper :as help] + [clojure.string :as str] + [clojure.test :refer [deftest is testing use-fixtures]] + [kerodon.core :refer [check choose fill-in press session visit within]] + [kerodon.test :refer [has text?]] + [net.cgrand.enlive-html :as enlive] + [peridot.core :as p])) + +(use-fixtures :each + help/default-fixture + help/with-clean-database + help/run-test-app) + +(deftest admin-can-add-member-to-group + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password") + ((fn [session] (email/expect-mock-emails 2) session)) + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "No")))) + + (is (some #{"fixture"} (db/group-membernames help/*db* "org.clojars.dantheman" db/SCOPE-ALL))) + + (help/match-audit {:username "dantheman"} + {:tag "permission-added" + :user "dantheman" + :group_name "org.clojars.dantheman" + :message "user 'fixture' added as member with '*' scope"}) + + (is (true? (email/wait-for-mock-emails))) + (is (= 2 (count @email/mock-emails))) + (is (= #{"fixture@example.org" "test@example.org"} + (into #{} (map first) @email/mock-emails))) + (is (every? #(= "A permission was added to the group org.clojars.dantheman" + %) + (into [] (map second) @email/mock-emails))) + (is (every? #(str/starts-with? % "User 'fixture' was added to the org.clojars.dantheman group with scope '*' by dantheman.\n\n") + (into [] (map #(nth % 2)) @email/mock-emails)))) + +(deftest admin-can-toggle-member-to-admin + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "No")))) + + (help/match-audit {:username "dantheman"} + {:tag "permission-added" + :user "dantheman" + :group_name "org.clojars.dantheman" + :message "user 'fixture' added as member with '*' scope"}) + + (email/expect-mock-emails 2) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (press "Toggle Admin") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes")))) + + (help/match-audit {:username "dantheman"} + {:tag "permission-added" + :user "dantheman" + :group_name "org.clojars.dantheman" + :message "user 'fixture' added as admin with '*' scope"}) + + (is (true? (email/wait-for-mock-emails))) + (is (= 2 (count @email/mock-emails))) + (is (= #{"fixture@example.org" "test@example.org"} + (into #{} (map first) @email/mock-emails))) + (is (every? #(= "An admin permission was added to the group org.clojars.dantheman" + %) + (into [] (map second) @email/mock-emails))) + (is (every? #(str/starts-with? % "User 'fixture' was added as an admin to the org.clojars.dantheman group with scope '*' by dantheman.\n\n") + (into [] (map #(nth % 2)) @email/mock-emails)))) + +(deftest admin-can-add-admin-to-group + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password") + ((fn [session] (email/expect-mock-emails 2) session)) + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes")))) + + (is (some #{"fixture"} (db/group-adminnames help/*db* "org.clojars.dantheman" db/SCOPE-ALL))) + + (help/match-audit {:username "dantheman"} + {:tag "permission-added" + :user "dantheman" + :group_name "org.clojars.dantheman" + :message "user 'fixture' added as admin with '*' scope"}) + + (is (true? (email/wait-for-mock-emails))) + (is (= 2 (count @email/mock-emails))) + (is (= #{"fixture@example.org" "test@example.org"} + (into #{} (map first) @email/mock-emails))) + (is (every? #(= "An admin permission was added to the group org.clojars.dantheman" + %) + (into [] (map second) @email/mock-emails))) + (is (every? #(str/starts-with? % "User 'fixture' was added as an admin to the org.clojars.dantheman group with scope '*' by dantheman.\n\n") + (into [] (map #(nth % 2)) @email/mock-emails)))) + +(deftest admin-can-remove-user-from-group + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + ((fn [session] (email/expect-mock-emails 2) session)) + (press "Add Permission") + ((fn [session] + ;; clear the add emails + (email/wait-for-mock-emails 1000) + ;; Then prep for the remove emails + (email/expect-mock-emails 2) + session)) + (press "Remove Permission")) + (help/match-audit {:username "dantheman"} + {:tag "permission-removed" + :user "dantheman" + :group_name "org.clojars.dantheman" + :message "user 'fixture' with scope '*' removed"}) + + (is (true? (email/wait-for-mock-emails))) + (is (= 2 (count @email/mock-emails))) + (is (= #{"fixture@example.org" "test@example.org"} + (into #{} (map first) @email/mock-emails))) + (is (every? #(= "A permission was removed from the group org.clojars.dantheman" + %) + (into [] (map second) @email/mock-emails))) + (is (every? #(str/starts-with? % "User 'fixture' was removed from the org.clojars.dantheman group with scope '*' by dantheman.\n\n") + (into [] (map #(nth % 2)) @email/mock-emails)))) + +(deftest user-must-exist-to-be-added-to-group + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "No such user: fixture"))))) + +(deftest user-can-be-scoped-to-jars + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test2" + :version "0.0.1"}) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))) + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (choose [:#scope_to_jar_select] "test2") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test2"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))))) + +(deftest user-can-be-toggled-with-scoping + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))) + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "No"))))) + +(deftest user-can-be-scoped-to-new-jar + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (choose [:#scope_to_jar_select] ":new") + (fill-in [:#scope_to_jar_new] "test") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))))) + +(deftest user-cannot-be-scoped-to-jar-when-is-all-scoped + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "fixture2" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (testing "As admin for all scope" + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))) + + ;; As admin for project scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (check [:#admin]) + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has '*' scope, so can't be further scoped"))) + + ;; As member for project scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has '*' scope, so can't be further scoped"))))) + + (testing "As member for all scope" + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture2"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "*"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "No"))) + + ;; As admin for project scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (choose [:#scope_to_jar_select] "test") + (check [:#admin]) + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has '*' scope, so can't be further scoped"))) + + ;; As member for project scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has '*' scope, so can't be further scoped")))))) + +(deftest user-cannot-be-scoped-to-all-when-caller-is-scoped-to-jar + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "fixture2" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (check [:#admin]) + (press "Add Permission")) + (-> (session (help/app)) + (login-as "fixture" "password") + ;; The UI hides invalid options from us, so we have to make the request + ;; manually + (p/request "/groups" :request-method :post + :params {:username "fixture2" + :admin "1"}) + (help/assert-status 403))) + +(deftest user-cannot-be-scoped-to-jar-that-caller-does-not-have-access-to + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "fixture2" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test2" + :version "0.0.1"}) + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (check [:#admin]) + (press "Add Permission")) + (-> (session (help/app)) + (login-as "fixture" "password") + ;; The UI hides invalid options from us, so we have to make the request + ;; manually + (p/request "/groups" :request-method :post + :params {:username "fixture2" + :scope_to_jar_select "test2" + :admin "1"}) + (help/assert-status 403))) + +(deftest user-cannot-be-scoped-to-all-when-is-jar-scoped + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "fixture2" "fixture@example.org" "password")) + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + ;; Add jars so they show in select + (db/add-jar help/*db* "dantheman" + {:group "org.clojars.dantheman" + :name "test" + :version "0.0.1"}) + (testing "As admin for jar scope" + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (choose [:#scope_to_jar_select] "test") + (check [:#admin]) + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "Yes"))) + + ;; As admin for all scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (check [:#admin]) + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has project scope, so can't be given '*' scope"))) + + ;; As member for project scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture") + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has project scope, so can't be given '*' scope"))))) + + (testing "As member for jar scope" + (-> (session (help/app)) + (login-as "dantheman" "password") + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (choose [:#scope_to_jar_select] "test") + (press "Add Permission") + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td enlive/first-of-type]] + (has (text? "fixture2"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 2)]] + (has (text? "test"))) + (within [:table.group-member-list + [:tr enlive/last-of-type] + [:td (enlive/nth-of-type 3)]] + (has (text? "No"))) + + ;; As admin for all scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (check [:#admin]) + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has project scope, so can't be given '*' scope"))) + + ;; As member for all scope + (visit "/groups/org.clojars.dantheman") + (fill-in [:#username] "fixture2") + (press "Add Permission") + (within [:div.error :ul :li] + (has (text? "User has project scope, so can't be given '*' scope")))))) + + diff --git a/test/clojars/integration/uploads_test.clj b/test/clojars/integration/uploads_test.clj index f2138503..2fc83443 100644 --- a/test/clojars/integration/uploads_test.clj +++ b/test/clojars/integration/uploads_test.clj @@ -18,7 +18,7 @@ [clojure.java.io :as io] [clojure.string :as str] [clojure.test :refer [are deftest is testing use-fixtures]] - [kerodon.core :refer [fill-in follow press session uncheck visit within]] + [kerodon.core :refer [choose fill-in follow follow-redirect press session uncheck visit within]] [kerodon.test :refer [has status? text?]] [net.cgrand.enlive-html :as enlive] [next.jdbc.sql :as sql]) @@ -406,7 +406,7 @@ (register-as "fixture" "fixture@example.org" "password")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] (is (thrown-with-msg? DeploymentException - #"Forbidden - You don't have access to the 'org\.clojars\.fixture' group" + #"Forbidden - You don't have access to the 'org\.clojars\.fixture/test' project" (deploy {:coordinates '[org.clojars.fixture/test "0.0.1"] :jar-file (io/file (io/resource "test.jar")) @@ -418,7 +418,7 @@ :group_name "org.clojars.fixture" :jar_name "test" :version "0.0.1" - :message "You don't have access to the 'org.clojars.fixture' group"}))) + :message "You don't have access to the 'org.clojars.fixture/test' project"}))) (deftest user-can-deploy-to-group-when-not-admin (-> (session (help/app)) @@ -427,7 +427,7 @@ (register-as "fixture" "fixture@example.org" "password") (visit "/groups/org.clojars.fixture") (fill-in [:#username] "dantheman") - (press "Add Member")) + (press "Add Permission")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] (deploy {:coordinates '[org.clojars.fixture/test "0.0.1"] @@ -438,6 +438,47 @@ ;; This test throws on failure, so we have this assertion to satisfy kaocha (is true)) +(deftest user-can-deploy-to-group-when-permission-scoped-to-the-project + (-> (session (help/app)) + (register-as "dantheman" "test@example.org" "password")) + (-> (session (help/app)) + (register-as "fixture" "fixture@example.org" "password")) + ;; Deploy once so the project exists to be scoped to below + (let [token (create-deploy-token (session (help/app)) "fixture" "password" "testing")] + (deploy + {:coordinates '[org.clojars.fixture/test "0.0.1"] + :jar-file (io/file (io/resource "test.jar")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:groupId "org.clojars.fixture"}) + :username "fixture" + :password token})) + (-> (session (help/app)) + (login-as "fixture" "password") + (follow-redirect) + (visit "/groups/org.clojars.fixture") + (fill-in [:#username] "dantheman") + (choose [:#scope_to_jar_select] "test") + (press "Add Permission")) + (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] + (deploy + {:coordinates '[org.clojars.fixture/test "0.0.2"] + :jar-file (io/file (io/resource "test.jar")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:groupId "org.clojars.fixture" + :version "0.0.2"}) + :password token}) + (testing "User can't deploy to another project in the group if it only has rights to a single project" + (is (thrown-with-msg? + DeploymentException + #"Forbidden - You don't have access to the 'org.clojars.fixture/test2' project" + (deploy + {:coordinates '[org.clojars.fixture/test2 "0.0.1"] + :jar-file (io/file (io/resource "test.jar")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:groupId "org.clojars.fixture" + :artifactId "test2"}) + :password token})))))) + (deftest user-cannot-deploy-to-a-non-existent-group (-> (session (help/app)) (register-as "dantheman" "test@example.org" "password")) @@ -467,8 +508,8 @@ ;; create a prior version in a non-verified group - we have to do ;; this directly and prevent a verified group check since we can ;; longer deploy to create the group or project - (db/add-admin help/*db* "legacy-group" "dantheman" "testing") - (with-redefs [db/check-group (constantly nil)] + (db/add-admin help/*db* "legacy-group" db/SCOPE-ALL "dantheman" "testing") + (with-redefs [db/check-group+project (constantly nil)] (db/add-jar help/*db* "dantheman" {:group "legacy-group" :name "test" :version "0.0.1-SNAPSHOT"})) @@ -500,7 +541,7 @@ (-> (session (help/app)) (register-as "dantheman" "test@example.org" "password")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] - (db/add-admin help/*db* "legacy-group" "dantheman" "testing") + (db/add-admin help/*db* "legacy-group" db/SCOPE-ALL "dantheman" "testing") (is (thrown-with-msg? DeploymentException #"Forbidden - Group 'legacy-group' isn't verified, so can't contain new projects. See https://bit.ly/3MuKGXO" @@ -1135,7 +1176,7 @@ (register-as "dantheman" "test@example.org" "password") (visit "/groups/org.clojars.dantheman") (fill-in [:#username] "donthemon") - (press "Add Member")) + (press "Add Permission")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] (email/expect-mock-emails 2) (deploy @@ -1165,7 +1206,7 @@ (register-as "dantheman" "test@example.org" "password") (visit "/groups/org.clojars.dantheman") (fill-in [:#username] "donthemon") - (press "Add Member")) + (press "Add Permission")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] (deploy {:coordinates '[org.clojars.dantheman/test "0.0.1"] @@ -1191,7 +1232,7 @@ (register-as "dantheman" "test@example.org" "password") (visit "/groups/org.clojars.dantheman") (fill-in [:#username] "donthemon") - (press "Add Member")) + (press "Add Permission")) (let [token (create-deploy-token (session (help/app)) "dantheman" "password" "testing")] (email/expect-mock-emails 1) (deploy diff --git a/test/clojars/integration/users_test.clj b/test/clojars/integration/users_test.clj index e9e6c6d8..8b11ee9b 100644 --- a/test/clojars/integration/users_test.clj +++ b/test/clojars/integration/users_test.clj @@ -1,15 +1,12 @@ (ns clojars.integration.users-test (:require - [clojars.db :as db] [clojars.email :as email] [clojars.integration.steps :refer [disable-mfa enable-mfa login-as register-as]] ;; for defmethods - [clojars.notifications.group] [clojars.notifications.user] [clojars.test-helper :as help] - [clojure.string :as str] [clojure.test :refer [deftest is testing use-fixtures]] - [kerodon.core :refer [check fill-in follow follow-redirect + [kerodon.core :refer [fill-in follow follow-redirect press session visit within]] [kerodon.test :refer [has status? text? value?]] [net.cgrand.enlive-html :as enlive])) @@ -276,121 +273,6 @@ (within [:p] (has (text? "The reset code was not found. Please ask for a new code in the forgot password page"))))) -(deftest admin-can-add-member-to-group - (-> (session (help/app)) - (register-as "fixture" "fixture@example.org" "password")) - (-> (session (help/app)) - (register-as "dantheman" "test@example.org" "password") - ((fn [session] (email/expect-mock-emails 2) session)) - (visit "/groups/org.clojars.dantheman") - (fill-in [:#username] "fixture") - (press "Add Member") - ;; (follow-redirect) - (within [:table.group-member-list - [:tr enlive/last-of-type] - [:td enlive/first-of-type]] - (has (text? "fixture"))) - (within [:table.group-member-list - [:tr enlive/last-of-type] - [:td (enlive/nth-of-type 2)]] - (has (text? "No")))) - - (is (some #{"fixture"} (db/group-membernames help/*db* "org.clojars.dantheman"))) - - (help/match-audit {:username "dantheman"} - {:tag "member-added" - :user "dantheman" - :group_name "org.clojars.dantheman" - :message "user 'fixture' added as member"}) - - (is (true? (email/wait-for-mock-emails))) - (is (= 2 (count @email/mock-emails))) - (is (= #{"fixture@example.org" "test@example.org"} - (into #{} (map first) @email/mock-emails))) - (is (every? #(= "A member was added to the group org.clojars.dantheman" - %) - (into [] (map second) @email/mock-emails))) - (is (every? #(str/starts-with? % "User 'fixture' was added to the org.clojars.dantheman group by dantheman.\n\n") - (into [] (map #(nth % 2)) @email/mock-emails)))) - -(deftest admin-can-add-admin-to-group - (-> (session (help/app)) - (register-as "fixture" "fixture@example.org" "password")) - (-> (session (help/app)) - (register-as "dantheman" "test@example.org" "password") - ((fn [session] (email/expect-mock-emails 2) session)) - (visit "/groups/org.clojars.dantheman") - (fill-in [:#username] "fixture") - (check [:#admin]) - (press "Add Member") - (within [:table.group-member-list - [:tr enlive/last-of-type] - [:td enlive/first-of-type]] - (has (text? "fixture"))) - (within [:table.group-member-list - [:tr enlive/last-of-type] - [:td (enlive/nth-of-type 2)]] - (has (text? "Yes")))) - - (is (some #{"fixture"} (db/group-adminnames help/*db* "org.clojars.dantheman"))) - - (help/match-audit {:username "dantheman"} - {:tag "member-added" - :user "dantheman" - :group_name "org.clojars.dantheman" - :message "user 'fixture' added as admin"}) - - (is (true? (email/wait-for-mock-emails))) - (is (= 2 (count @email/mock-emails))) - (is (= #{"fixture@example.org" "test@example.org"} - (into #{} (map first) @email/mock-emails))) - (is (every? #(= "An admin member was added to the group org.clojars.dantheman" - %) - (into [] (map second) @email/mock-emails))) - (is (every? #(str/starts-with? % "User 'fixture' was added as an admin to the org.clojars.dantheman group by dantheman.\n\n") - (into [] (map #(nth % 2)) @email/mock-emails)))) - -(deftest admin-can-remove-user-from-group - (-> (session (help/app)) - (register-as "fixture" "fixture@example.org" "password")) - (-> (session (help/app)) - (register-as "dantheman" "test@example.org" "password") - (visit "/groups/org.clojars.dantheman") - (fill-in [:#username] "fixture") - ((fn [session] (email/expect-mock-emails 2) session)) - (press "Add Member") - ((fn [session] - ;; clear the add emails - (email/wait-for-mock-emails 1000) - ;; Then prep for the remove emails - (email/expect-mock-emails 2) - session)) - (press "Remove Member")) - (help/match-audit {:username "dantheman"} - {:tag "member-removed" - :user "dantheman" - :group_name "org.clojars.dantheman" - :message "user 'fixture' removed"}) - - (is (true? (email/wait-for-mock-emails))) - (is (= 2 (count @email/mock-emails))) - (is (= #{"fixture@example.org" "test@example.org"} - (into #{} (map first) @email/mock-emails))) - (is (every? #(= "A member was removed from the group org.clojars.dantheman" - %) - (into [] (map second) @email/mock-emails))) - (is (every? #(str/starts-with? % "User 'fixture' was removed from the org.clojars.dantheman group by dantheman.\n\n") - (into [] (map #(nth % 2)) @email/mock-emails)))) - -(deftest user-must-exist-to-be-added-to-group - (-> (session (help/app)) - (register-as "dantheman" "test@example.org" "password") - (visit "/groups/org.clojars.dantheman") - (fill-in [:#username] "fixture") - (press "Add Member") - (within [:div.error :ul :li] - (has (text? "No such user: fixture"))))) - (deftest users-can-be-viewed (-> (session (help/app)) (register-as "dantheman" "test@example.org" "password") diff --git a/test/clojars/test_helper.clj b/test/clojars/test_helper.clj index c35bde45..0e56e3c8 100644 --- a/test/clojars/test_helper.clj +++ b/test/clojars/test_helper.clj @@ -113,13 +113,14 @@ (binding [config/*profile* "test"] ;; double binding since ^ needs to be bound for config to load ;; properly - (binding [system (component/start (assoc (system/new-system (config/config)) - :repo-bucket (s3/mock-s3-client) - :error-reporter (quiet-reporter) - :index-factory memory-index - :mailer (email/mock-mailer) - :stats (no-stats) - :github (oauth-service/new-mock-oauth-service "GitHub" {})))] + (binding [system (component/start + (assoc (system/new-system (config/config)) + :repo-bucket (s3/mock-s3-client) + :error-reporter (quiet-reporter) + :index-factory memory-index + :mailer (email/mock-mailer) + :stats (no-stats) + :github (oauth-service/new-mock-oauth-service "GitHub" {})))] (let [db (get-in system [:db :spec])] (try (clear-database db) @@ -203,3 +204,7 @@ (Thread/sleep 1000) (recur (inc attempt))) false)))) + +(defn assert-status + [session status] + (is (= status (get-in session [:response :status])))) diff --git a/test/clojars/unit/db_test.clj b/test/clojars/unit/db_test.clj index 398279b0..89498dc3 100644 --- a/test/clojars/unit/db_test.clj +++ b/test/clojars/unit/db_test.clj @@ -92,12 +92,12 @@ name "testuser" password "password"] (db/add-user help/*db* email name password) - (is (= ["testuser"] (db/group-adminnames help/*db* (str "org.clojars." name)))) + (is (= ["testuser"] (db/group-adminnames help/*db* (str "org.clojars." name) db/SCOPE-ALL))) (is (= ["testuser"] (db/group-activenames help/*db* (str "org.clojars." name)))) - (is (= [] (db/group-membernames help/*db* (str "org.clojars." name)))) - (is (= ["testuser"] (db/group-adminnames help/*db* (str "net.clojars." name)))) + (is (= [] (db/group-membernames help/*db* (str "org.clojars." name) db/SCOPE-ALL))) + (is (= ["testuser"] (db/group-adminnames help/*db* (str "net.clojars." name) db/SCOPE-ALL))) (is (= ["testuser"] (db/group-activenames help/*db* (str "net.clojars." name)))) - (is (= [] (db/group-membernames help/*db* (str "net.clojars." name)))) + (is (= [] (db/group-membernames help/*db* (str "net.clojars." name) db/SCOPE-ALL))) (is (= ["net.clojars.testuser" "org.clojars.testuser"] (db/find-groupnames help/*db* name))))) @@ -106,22 +106,36 @@ name "testuser" password "password"] (db/add-user help/*db* email name password) - (db/add-member help/*db* "test-group" name "some-dude") + (db/add-member help/*db* "test-group" db/SCOPE-ALL name "some-dude") (is (= ["testuser"] (db/group-activenames help/*db* "test-group"))) - (is (= ["testuser"] (db/group-membernames help/*db* "test-group"))) - (is (= [] (db/group-adminnames help/*db* "test-group"))) - (is (some #{"test-group"} (db/find-groupnames help/*db* name))))) + (is (= ["testuser"] (db/group-membernames help/*db* "test-group" db/SCOPE-ALL))) + (is (= [] (db/group-adminnames help/*db* "test-group" db/SCOPE-ALL))) + (is (some #{"test-group"} (db/find-groupnames help/*db* name))) + + (db/add-member help/*db* "test-group2" "a-project" name "some-dude") + (is (= ["testuser"] (db/group-activenames help/*db* "test-group2"))) + (is (= ["testuser"] (db/group-membernames help/*db* "test-group2" "a-project"))) + (is (= [] (db/group-membernames help/*db* "test-group2" db/SCOPE-ALL))) + (is (= [] (db/group-adminnames help/*db* "test-group2" "a-project"))) + (is (some #{"test-group2"} (db/find-groupnames help/*db* name))))) (deftest admins-can-be-added-to-groups (let [email "test@example.com" name "testadmin" password "password"] (db/add-user help/*db* email name password) - (db/add-admin help/*db* "test-group" name "some-dude") + (db/add-admin help/*db* "test-group" db/SCOPE-ALL name "some-dude") (is (= ["testadmin"] (db/group-activenames help/*db* "test-group"))) - (is (= [] (db/group-membernames help/*db* "test-group"))) - (is (= ["testadmin"] (db/group-adminnames help/*db* "test-group"))) - (is (some #{"test-group"} (db/find-groupnames help/*db* name))))) + (is (= [] (db/group-membernames help/*db* "test-group" db/SCOPE-ALL))) + (is (= ["testadmin"] (db/group-adminnames help/*db* "test-group" db/SCOPE-ALL))) + (is (some #{"test-group"} (db/find-groupnames help/*db* name))) + + (db/add-admin help/*db* "test-group2" "a-project" name "some-dude") + (is (= ["testadmin"] (db/group-activenames help/*db* "test-group2"))) + (is (= [] (db/group-membernames help/*db* "test-group2" "a-project"))) + (is (= ["testadmin"] (db/group-adminnames help/*db* "test-group2" "a-project"))) + (is (= [] (db/group-adminnames help/*db* db/SCOPE-ALL "a-project"))) + (is (some #{"test-group2"} (db/find-groupnames help/*db* name))))) ;; TODO: Tests below should have the users added first. ;; Currently user unenforced foreign keys are by name @@ -333,7 +347,7 @@ :group_name name}] (help/add-verified-group "test-user" name) (help/add-verified-group "test-user2" "tester-group") - (db/add-member help/*db* name "test-user2" "some-user") + (db/add-member help/*db* name db/SCOPE-ALL "test-user2" "some-user") (help/with-time (Timestamp. 0) (db/add-jar help/*db* "test-user" jarmap)) (help/with-time (Timestamp. 1) @@ -375,7 +389,7 @@ :group_name name}] (help/add-verified-group "test-user" name) (help/add-verified-group "test-user" "tester-group") - (db/add-member help/*db* name "test-user2" "some-user") + (db/add-member help/*db* name db/SCOPE-ALL "test-user2" "some-user") (help/with-time (Timestamp. 0) (db/add-jar help/*db* "test-user" jarmap)) (help/with-time (Timestamp. 1) @@ -394,7 +408,7 @@ (deftest add-jar-validates-group-permissions (let [jarmap {:name "jar-name" :version "1" :group "group-name"}] - (db/add-member help/*db* "group-name" "some-user" "some-dude") + (db/add-member help/*db* "group-name" db/SCOPE-ALL "some-user" "some-dude") (is (thrown? Exception (db/add-jar help/*db* "test-user" jarmap))))) (deftest recent-jars-returns-6-most-recent-jars-only-most-recent-version diff --git a/test/clojars/unit/friend/oauth/github_test.clj b/test/clojars/unit/friend/oauth/github_test.clj index 8c831bd2..23689033 100644 --- a/test/clojars/unit/friend/oauth/github_test.clj +++ b/test/clojars/unit/friend/oauth/github_test.clj @@ -85,7 +85,7 @@ (is (db/find-group-verification help/*db* "io.github.jd2")))) (testing "with a valid user but group already exists" - (db/add-admin help/*db* "com.github.johnd" "someone" "clojars") + (db/add-admin help/*db* "com.github.johnd" db/SCOPE-ALL "someone" "clojars") (set-mock-responses [{:email "john.doe@example.org" :primary true diff --git a/test/clojars/unit/friend/oauth/gitlab_test.clj b/test/clojars/unit/friend/oauth/gitlab_test.clj index 373548fb..404a7bff 100644 --- a/test/clojars/unit/friend/oauth/gitlab_test.clj +++ b/test/clojars/unit/friend/oauth/gitlab_test.clj @@ -75,7 +75,7 @@ (is (db/find-group-verification help/*db* "io.gitlab.jd2")))) (testing "with a valid user but group already exists" - (db/add-admin help/*db* "com.gitlab.johnd" "someone" "clojars") + (db/add-admin help/*db* "com.gitlab.johnd" db/SCOPE-ALL "someone" "clojars") (set-mock-responses "john.doe@example.org" "johnd") (let [req {:uri "/oauth/gitlab/callback" diff --git a/test/clojars/unit/verification_test.clj b/test/clojars/unit/verification_test.clj index 965e7859..87a8976c 100644 --- a/test/clojars/unit/verification_test.clj +++ b/test/clojars/unit/verification_test.clj @@ -87,8 +87,8 @@ (db/add-group help/*db* "dantheman" "com.foo") ;; We have to have some other member for the group to be considered to ;; exist. - (db/add-member help/*db* "com.foo" "anotheruser" "testing") - (db/inactivate-member help/*db* "com.foo" "dantheman" "testing") + (db/add-member help/*db* "com.foo" db/SCOPE-ALL "anotheruser" "testing") + (db/inactivate-member help/*db* "com.foo" db/SCOPE-ALL "dantheman" "testing") (help/with-TXT records (is (match? {:txt-records records