diff --git a/project.clj b/project.clj index e23826ed..03a443e3 100644 --- a/project.clj +++ b/project.clj @@ -6,7 +6,7 @@ [org.apache.maven/maven-model "3.0.4" :exclusions [org.codehaus.plexus/plexus-utils]] - [com.cemerick/pomegranate "0.0.13" + [com.cemerick/pomegranate "0.3.0" :exclusions [org.apache.httpcomponents/httpcore commons-logging]] diff --git a/src/clojars/auth.clj b/src/clojars/auth.clj index f38f3b52..8e80ba66 100644 --- a/src/clojars/auth.clj +++ b/src/clojars/auth.clj @@ -2,21 +2,19 @@ (:require [cemerick.friend :as friend] [clojars.db :refer [group-membernames]])) -(defmacro with-account [body] - `(friend/authenticated (try-account ~body))) +(defn try-account [f] + (f (:username (friend/current-authentication)))) -(defmacro try-account [body] - `(let [~'account (:username (friend/current-authentication))] - ~body)) +(defn with-account [f] + (friend/authenticated (try-account f))) (defn authorized? [db account group] (if account (let [names (group-membernames db group)] (or (some #{account} names) (empty? names))))) -(defmacro require-authorization [db group & body] - `(if (authorized? ~db ~'account ~group) - (do ~@body) - (friend/throw-unauthorized friend/*identity* - {:cemerick.friend/exprs (quote [~@body]) - :cemerick.friend/required-roles ~group}))) +(defn require-authorization [db account group f] + (if (authorized? db account group) + (f) + (friend/throw-unauthorized friend/*identity* + {:cemerick.friend/required-roles group}))) diff --git a/src/clojars/file_utils.clj b/src/clojars/file_utils.clj index 9f5fa89c..7056914a 100644 --- a/src/clojars/file_utils.clj +++ b/src/clojars/file_utils.clj @@ -7,7 +7,7 @@ [file type] (let [file' (io/file file)] (io/file (.getParentFile file') - (format "%s.%s" (.getName file') (name type))))) + (format "%s.%s" (.getName file') (name type))))) (defn- create-sum [f file type] (let [file' (io/file file)] @@ -35,17 +35,22 @@ (defn valid-sum? "Checks to see if a sum of type `type` exists and is valid for `file`" - [file type] - (let [sig-file (sum-file file type)] - (and (.exists sig-file) - (= ((sum-generators type) (io/file file)) - (slurp sig-file))))) + ([file type] + (valid-sum? file type true)) + ([file type fail-if-missing?] + (let [sig-file (sum-file file type)] + (if (.exists sig-file) + (= ((sum-generators type) (io/file file)) + (slurp sig-file)) + (not fail-if-missing?))))) (defn valid-sums? "Checks to see if both md5 and sha1 sums exist and are valid for `file`" - [file] - (reduce (fn [valid? sig-type] - (and valid? - (valid-sum? file sig-type))) - true - [:md5 :sha1])) \ No newline at end of file + ([file] + (valid-sums? file true)) + ([file fail-if-missing?] + (reduce (fn [valid? sig-type] + (and valid? + (valid-sum? file sig-type fail-if-missing?))) + true + [:md5 :sha1]))) diff --git a/src/clojars/maven.clj b/src/clojars/maven.clj index 2d4c27fd..c1632002 100644 --- a/src/clojars/maven.clj +++ b/src/clojars/maven.clj @@ -48,6 +48,7 @@ :licenses (mapv license-to-seq (.getLicenses model)) :scm (scm-to-map (.getScm model)) :authors (mapv #(.getName %) (.getContributors model)) + :packaging (keyword (.getPackaging model)) :dependencies (mapv (fn [d] {:group_name (.getGroupId d) :jar_name (.getArtifactId d) diff --git a/src/clojars/routes/artifact.clj b/src/clojars/routes/artifact.clj index 7d9cf8e6..10f15dd9 100644 --- a/src/clojars/routes/artifact.clj +++ b/src/clojars/routes/artifact.clj @@ -10,31 +10,26 @@ (defn show [db reporter stats group-id artifact-id] (if-let [artifact (db/find-jar db group-id artifact-id)] (auth/try-account - (view/show-jar db - reporter - stats - account - artifact - (db/recent-versions db group-id artifact-id 5) - (db/count-versions db group-id artifact-id))))) + #(view/show-jar db + reporter + stats + % + artifact + (db/recent-versions db group-id artifact-id 5) + (db/count-versions db group-id artifact-id))))) (defn list-versions [db group-id artifact-id] (if-let [artifact (db/find-jar db group-id artifact-id)] (auth/try-account - (view/show-versions account - artifact - (db/recent-versions db group-id artifact-id))))) + #(view/show-versions % artifact + (db/recent-versions db group-id artifact-id))))) (defn show-version [db reporter stats group-id artifact-id version] (if-let [artifact (db/find-jar db group-id artifact-id version)] (auth/try-account - (view/show-jar db - reporter - stats - account - artifact - (db/recent-versions db group-id artifact-id 5) - (db/count-versions db group-id artifact-id))))) + #(view/show-jar db reporter stats % artifact + (db/recent-versions db group-id artifact-id 5) + (db/count-versions db group-id artifact-id))))) (defn response-based-on-format "render appropriate response based on the file type suffix provided: diff --git a/src/clojars/routes/group.clj b/src/clojars/routes/group.clj index 7d41b83d..98b41d2f 100644 --- a/src/clojars/routes/group.clj +++ b/src/clojars/routes/group.clj @@ -10,22 +10,24 @@ (GET ["/groups/:groupname", :groupname #"[^/]+"] [groupname] (if-let [membernames (seq (db/group-membernames db groupname))] (auth/try-account - (view/show-group db account groupname membernames)))) + #(view/show-group db % groupname membernames)))) (POST ["/groups/:groupname", :groupname #"[^/]+"] [groupname username] (if-let [membernames (seq (db/group-membernames db groupname))] (auth/try-account - (auth/require-authorization - db - groupname - (cond - (some #{username} membernames) - (view/show-group db account groupname membernames - "They're already a member!") - (db/find-user db username) - (do (db/add-member db groupname username account) - (view/show-group db account groupname - (conj membernames username))) - :else - (view/show-group db account groupname membernames - (str "No such user: " - username))))))))) + (fn [account] + (auth/require-authorization + db + account + groupname + #(cond + (some #{username} membernames) + (view/show-group db account groupname membernames + "They're already a member!") + (db/find-user db username) + (do (db/add-member db groupname username account) + (view/show-group db account groupname + (conj membernames username))) + :else + (view/show-group db account groupname membernames + (str "No such user: " + username)))))))))) diff --git a/src/clojars/routes/repo.clj b/src/clojars/routes/repo.clj index c4524ece..9337d099 100644 --- a/src/clojars/routes/repo.clj +++ b/src/clojars/routes/repo.clj @@ -4,18 +4,21 @@ [config :refer [config]] [db :as db] [errors :refer [report-error]] + [file-utils :as fu] [maven :as maven] [search :as search]] [clojure.java.io :as io] [clojure.string :as string] + [cemerick.pomegranate.aether :as aether] [compojure [core :as compojure :refer [PUT defroutes]] [route :refer [not-found]]] [ring.util [codec :as codec] - [response :as response]] - [clojars.maven :as mvn]) - (:import java.io.StringReader)) + [response :as response]]) + (:import java.io.StringReader + java.util.UUID + org.apache.commons.io.FileUtils)) (defn versions [group-id artifact-id] (->> (.listFiles (io/file (config :repo) group-id artifact-id)) @@ -46,59 +49,131 @@ (.delete sent-file) (throw e)))) -(defn- pom? [filename] - (.endsWith filename ".pom")) - -(defn- get-pom-info [contents info] - (-> contents - StringReader. - maven/pom-to-map - (merge info))) - -(defn- body-and-add-pom [db body filename info account] - (if (pom? filename) - (let [contents (slurp body)] - (db/add-jar db account (get-pom-info contents info)) - contents) - body)) - -(defmacro put-req [db groupname & body] - `(with-account - (require-authorization - ~db - ~groupname - ~@body - ;; should we only do 201 if the file didn't already exist? - {:status 201 :headers {} :body nil}))) +(defn- pom? [file] + (let [filename (if (string? file) file (.getName file))] + (.endsWith filename ".pom"))) + +(defn find-upload-dir [{:keys [upload-dir]}] + (let [dir (io/file upload-dir)] + (if (and dir (.exists dir)) + dir + (let [dir' (io/file (FileUtils/getTempDirectory) + (str "upload-" (UUID/randomUUID)))] + (FileUtils/forceMkdir dir') + dir')))) + +(defn upload-request [db groupname session f] + (with-account + (fn [account] + (let [upload-dir (find-upload-dir session)] + (require-authorization db account groupname (partial f account upload-dir)) + ;; should we only do 201 if the file didn't already exist? + {:status 201 + :headers {} + :session (assoc session :upload-dir (.getAbsolutePath upload-dir)) + :body nil})))) + +(defn find-pom [dir] + (->> dir + file-seq + (filter pom?) + first)) + +;; borrowed from +;; https://github.com/technomancy/leiningen/tree/2.5.3/src/leiningen/deploy.clj#L137 +;; and modified +(defn- extension [f] + (let [name (.getName f)] + (if-let [[_ signed-extension] (re-find #"\.([a-z]+\.asc)$" name)] + signed-extension + (last (.split name "\\."))))) + +(defn- match-file-name [re f] + (re-find re (.getName f))) + +(defn find-artifacts [dir] + (into [] + (comp + (filter (memfn isFile)) + (remove (partial match-file-name #".sha1$")) + (remove (partial match-file-name #".md5$")) + (remove (partial match-file-name #"^maven-metadata\.xml")) + (remove (partial match-file-name #"^metadata\.edn$"))) + (file-seq dir))) + +(defn- throw-invalid + ([message] + (throw-invalid message nil)) + ([message meta] + (throw-invalid message meta nil)) + ([message meta cause] + (throw + (ex-info message (merge {:report? false} meta) cause)))) (defn- validate-regex [x re message] (when-not (re-matches re x) - (throw (ex-info message {:value x - :regex re})))) + (throw-invalid message {:value x + :regex re}))) + +(defn- validate-pom-entry [pom-data key value] + (when-not (= (key pom-data) value) + (throw-invalid + (format "the %s in the pom (%s) does not match the %s you are deploying to (%s)" + (name key) (key pom-data) (name key) value) + {:pom pom-data}))) + +(defn- validate-pom [pom group name version] + (validate-pom-entry pom :group group) + (validate-pom-entry pom :name name) + (validate-pom-entry pom :version version)) (defn snapshot-version? [version] (.endsWith version "-SNAPSHOT")) -(defn assert-non-redeploy [group-id artifact-id version filename] +(defn assert-non-redeploy [group-id artifact-id version] (when (and (not (snapshot-version? version)) (.exists (io/file (config :repo) (string/replace group-id "." "/") - artifact-id version filename))) - (throw (ex-info "redeploying non-snapshots is not allowed (see http://git.io/vO2Tg)" - {:report? false})))) + artifact-id version))) + (throw-invalid "redeploying non-snapshots is not allowed (see http://git.io/vO2Tg)"))) -(defn validate-deploy [group-id artifact-id version filename] - (try +(defn assert-jar-uploaded [artifacts pom] + (when (and (= :jar (:packaging pom)) + (not (some (partial match-file-name #"\.jar$") artifacts))) + (throw-invalid "no jar file was uploaded"))) + +(defn validate-checksums [artifacts] + (doseq [f artifacts] + ;; verify that at least one type checksum file exists + (when (not (or (.exists (fu/sum-file f :md5)) + (.exists (fu/sum-file f :sha1)))) + (throw-invalid (str "no checksum provided for " (.getName f) {:file f}))) + ;; verify provided checksums are valid + (when (not (fu/valid-sums? f false)) + (throw-invalid (str "invalid checksum for " (.getName f) {:file f}))))) + +(defn assert-signatures [artifacts] + ;; if any signatures exist, require them for every artifact + (let [asc-matcher (partial match-file-name #"\.asc$")] + (when (some asc-matcher artifacts) + (doseq [f artifacts + :when (not (asc-matcher f)) + :when (not (.exists (io/file (str (.getAbsolutePath f) ".asc"))))] + (throw-invalid (format "%s has no signature" (.getName f)) {:file f}))))) + +(defn validate-gav [group name version] ;; We're on purpose *at least* as restrictive as the recommendations on ;; https://maven.apache.org/guides/mini/guide-naming-conventions.html ;; If you want loosen these please include in your proposal the ;; ramifications on usability, security and compatiblity with filesystems, ;; OSes, URLs and tools. - (validate-regex artifact-id #"^[a-z0-9_.-]+$" + (validate-regex name #"^[a-z0-9_.-]+$" (str "project names must consist solely of lowercase " "letters, numbers, hyphens and underscores (see http://git.io/vO2Uy)")) - (validate-regex group-id #"^[a-z0-9_.-]+$" + + (validate-regex group #"^[a-z0-9_.-]+$" (str "group names must consist solely of lowercase " "letters, numbers, hyphens and underscores (see http://git.io/vO2Uy)")) + ;; Maven's pretty accepting of version numbers, but so far in 2.5 years ;; bar one broken non-ascii exception only these characters have been used. ;; Even if we manage to support obscure characters some filesystems do not @@ -106,42 +181,74 @@ ;; compatible for everyone let's lock it down. (validate-regex version #"^[a-zA-Z0-9_.+-]+$" (str "version strings must consist solely of letters, " - "numbers, dots, pluses, hyphens and underscores (see http://git.io/vO2TO)")) - (assert-non-redeploy group-id artifact-id version filename) + "numbers, dots, pluses, hyphens and underscores (see http://git.io/vO2TO)"))) + +(defn validate-deploy [dir pom {:keys [group name version]}] + (try + (validate-gav group name version) + (validate-pom pom group name version) + (assert-non-redeploy group name version) + + (let [artifacts (find-artifacts dir)] + (assert-jar-uploaded artifacts pom) + (validate-checksums artifacts) + (assert-signatures artifacts)) + (catch Exception e (throw (ex-info (.getMessage e) (merge {:status 403 :status-message (str "Forbidden - " (.getMessage e)) - :group-id group-id - :artifact-id artifact-id - :version version - :file filename} - (ex-data e))))))) + :group group + :name name + :version version} + (ex-data e)) + (.getCause e)))))) + +(defn finalize-deploy [db search account repo dir] + (try + (if-let [pom-file (find-pom dir)] + (let [pom (try + (maven/pom-to-map pom-file) + (catch Exception e + (throw-invalid (str "invalid pom file: " (.getMessage e)) + {:file pom-file} + e))) + {:keys [group name version] :as posted-metadata} (read-string (slurp (io/file dir "metadata.edn")))] + (validate-deploy dir pom posted-metadata) + (db/check-and-add-group db account group) + (aether/deploy + :coordinates [(symbol group name) version] + :artifact-map (reduce #(assoc %1 + [:extension (extension %2)] %2) + {} (find-artifacts dir)) + :repository {"local" {:url (-> repo io/file .toURI .toURL)}}) + (db/add-jar db account pom) + (search/index! search (assoc pom + :at (.lastModified pom-file)))) + (throw-invalid "no pom file was uploaded")) + (finally + (FileUtils/deleteQuietly dir)))) -(defn- handle-versioned-upload [db search body group artifact version filename] +(defn- handle-versioned-upload [db body session group artifact version filename] (let [groupname (string/replace group "/" ".")] - (put-req + (upload-request db groupname - (let [file (io/file (config :repo) group artifact version filename) - info {:group groupname - :name artifact - :version version}] - (validate-deploy groupname artifact version filename) - (db/check-and-add-group db account groupname) - - (try-save-to-file file (body-and-add-pom db body filename info account)) - (when (pom? filename) - (search/index! search (assoc (mvn/pom-to-map file) - :at (.lastModified file)))))))) + session + (fn [_ upload-dir] + (spit (io/file upload-dir "metadata.edn") + (pr-str {:group groupname + :name artifact + :version version})) + (try-save-to-file (io/file upload-dir group artifact version filename) body))))) ;; web handlers (defn routes [db search] (compojure/routes (PUT ["/:group/:artifact/:file" :group #".+" :artifact #"[^/]+" :file #"maven-metadata\.xml[^/]*"] - {body :body {:keys [group artifact file]} :params} + {body :body session :session {:keys [group artifact file]} :params} (if (snapshot-version? artifact) ;; SNAPSHOT metadata will hit this route, but should be ;; treated as a versioned file upload. @@ -150,19 +257,24 @@ group-parts (string/split group #"/") group (string/join "/" (butlast group-parts)) artifact (last group-parts)] - (handle-versioned-upload db search body group artifact version file)) - (let [groupname (string/replace group "/" ".")] - (put-req - db - groupname - (let [file (io/file (config :repo) group artifact file)] - (db/check-and-add-group db account groupname) - (try-save-to-file file body)))))) + (handle-versioned-upload db body session group artifact version file)) + (when (re-find #"maven-metadata\.xml$" file) + ;; ignore metadata sums, since we'll recreate those when + ;; the deploy is finalizied + (let [groupname (string/replace group "/" ".")] + (upload-request + db + groupname + session + (fn [account upload-dir] + (let [file (io/file upload-dir group artifact file)] + (try-save-to-file file body) + (finalize-deploy db search account (config :repo) upload-dir)))))))) (PUT ["/:group/:artifact/:version/:filename" :group #"[^\.]+" :artifact #"[^/]+" :version #"[^/]+" :filename #"[^/]+(\.pom|\.jar|\.sha1|\.md5|\.asc)$"] - {body :body {:keys [group artifact version filename]} :params} - (handle-versioned-upload db search body group artifact version filename)) + {body :body session :session {:keys [group artifact version filename]} :params} + (handle-versioned-upload db body session group artifact version filename)) (PUT "*" _ {:status 400 :headers {}}) (not-found "Page not found"))) diff --git a/src/clojars/routes/user.clj b/src/clojars/routes/user.clj index dc86ddc9..269717f1 100644 --- a/src/clojars/routes/user.clj +++ b/src/clojars/routes/user.clj @@ -7,16 +7,16 @@ (defn show [db username] (if-let [user (db/find-user db username)] (auth/try-account - (view/show-user db account user)))) + #(view/show-user db % user)))) (defn routes [db mailer] (compojure/routes (GET "/profile" {:keys [flash]} (auth/with-account - (view/profile-form account (db/find-user db account) flash))) + #(view/profile-form % (db/find-user db %) flash))) (POST "/profile" {:keys [params]} (auth/with-account - (view/update-profile db account params))) + #(view/update-profile db % params))) (GET "/register" {:keys [params]} (view/register-form params)) diff --git a/src/clojars/web.clj b/src/clojars/web.clj index 514020cc..c59a3efa 100644 --- a/src/clojars/web.clj +++ b/src/clojars/web.clj @@ -43,23 +43,23 @@ (defn main-routes [db reporter stats search-obj mailer] (routes (GET "/" _ - (try-account - (if account - (dashboard db account) - (index-page db stats account)))) + (try-account + #(if % + (dashboard db %) + (index-page db stats %)))) (GET "/search" {:keys [params]} (try-account - (let [validated-params (if (:page params) - (assoc params :page (Integer. (:page params))) - params)] - (search search-obj account validated-params)))) + #(let [validated-params (if (:page params) + (assoc params :page (Integer. (:page params))) + params)] + (search search-obj % validated-params)))) (GET "/projects" {:keys [params]} (try-account - (browse db account params))) + #(browse db % params))) (GET "/security" [] (try-account - (html-doc "Security" {:account account} - (raw (slurp (io/resource "security.html")))))) + #(html-doc "Security" {:account %} + (raw (slurp (io/resource "security.html")))))) session/routes (group/routes db) (artifact/routes db reporter stats) @@ -71,11 +71,11 @@ (PUT "*" _ {:status 405 :headers {} :body "Did you mean to use /repo?"}) (ANY "*" _ (try-account - (not-found - (html-doc "Page not found" {:account account} - [:div.small-section - [:h1 "Page not found"] - [:p "Thundering typhoons! I think we lost it. Sorry!"]])))))) + #(not-found + (html-doc "Page not found" {:account %} + [:div.small-section + [:h1 "Page not found"] + [:p "Thundering typhoons! I think we lost it. Sorry!"]])))))) (defn bad-attempt [attempts user] (let [failures (or (attempts user) 0)] @@ -102,17 +102,18 @@ (defn clojars-app [db reporter stats search mailer] (routes - (context "/repo" _ - (-> (repo/routes db search) - (friend/authenticate - {:credential-fn (credential-fn db) - :workflows [(workflows/http-basic :realm "clojars")] - :allow-anon? false - :unauthenticated-handler - (partial workflows/http-basic-deny "clojars")}) - (repo/wrap-exceptions reporter) - (repo/wrap-file (:repo config)) - (repo/wrap-reject-double-dot))) + (-> (context "/repo" _ + (-> (repo/routes db search) + (friend/authenticate + {:credential-fn (credential-fn db) + :workflows [(workflows/http-basic :realm "clojars")] + :allow-anon? false + :unauthenticated-handler + (partial workflows/http-basic-deny "clojars")}) + (repo/wrap-exceptions reporter) + (repo/wrap-file (:repo config)) + (repo/wrap-reject-double-dot))) + (wrap-secure-session)) (-> (main-routes db reporter stats search mailer) (friend/authenticate {:credential-fn (credential-fn db) diff --git a/test/clojars/test/integration/uploads.clj b/test/clojars/test/integration/uploads.clj index e4fea0b3..05493236 100644 --- a/test/clojars/test/integration/uploads.clj +++ b/test/clojars/test/integration/uploads.clj @@ -23,9 +23,10 @@ (help/delete-file-recursively help/local-repo) (help/delete-file-recursively help/local-repo2) (aether/deploy - :coordinates '[org.clojars.dantheman/test "1.0.0"] + :coordinates '[org.clojars.dantheman/test "0.0.1"] :jar-file (io/file (io/resource "test.jar")) - :pom-file (io/file (io/resource "test-0.0.1/test.pom")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:groupId "org.clojars.dantheman"}) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") :username "dantheman" :password "password"}} @@ -36,10 +37,10 @@ "clojars" "dantheman" "test" - "1.0.0"))))) - (is (= '{[org.clojars.dantheman/test "1.0.0"] nil} + "0.0.1"))))) + (is (= '{[org.clojars.dantheman/test "0.0.1"] nil} (aether/resolve-dependencies - :coordinates '[[org.clojars.dantheman/test "1.0.0"]] + :coordinates '[[org.clojars.dantheman/test "0.0.1"]] :repositories {"test" {:url (str "http://localhost:" help/test-port "/repo")}} :local-repo help/local-repo2))) @@ -59,10 +60,8 @@ (visit "/") (fill-in [:#search] "test") (press [:#search-button]) - ;; the pom is used as is, even if the data is wrong - ;; https://github.com/clojars/clojars-web/issues/358 (within [:div.result] - (has (text? "fake/test 0.0.1"))))) + (has (text? "org.clojars.dantheman/test 0.0.1"))))) (deftest user-can-deploy-to-new-group (-> (session (help/app-from-system)) @@ -116,7 +115,7 @@ (-> (session (help/app-from-system)) (register-as "dantheman" "test@example.org" "password")) (aether/deploy - :coordinates '[org.clojars.dantheman/test "0.0.1"] + :coordinates '[fake/test "0.0.1"] :jar-file (io/file (io/resource "test.jar")) :pom-file (io/file (io/resource "test-0.0.1/test.pom")) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") @@ -127,7 +126,7 @@ org.sonatype.aether.deployment.DeploymentException #"Forbidden - redeploying non-snapshots" (aether/deploy - :coordinates '[org.clojars.dantheman/test "0.0.1"] + :coordinates '[fake/test "0.0.1"] :jar-file (io/file (io/resource "test.jar")) :pom-file (io/file (io/resource "test-0.0.1/test.pom")) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") @@ -139,7 +138,7 @@ (-> (session (help/app-from-system)) (register-as "dantheman" "test@example.org" "password")) (aether/deploy - :coordinates '[org.clojars.dantheman/test "0.0.3-SNAPSHOT"] + :coordinates '[fake/test "0.0.3-SNAPSHOT"] :jar-file (io/file (io/resource "test.jar")) :pom-file (io/file (io/resource "test-0.0.3-SNAPSHOT/test.pom")) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") @@ -147,7 +146,7 @@ :password "password"}} :local-repo help/local-repo) (aether/deploy - :coordinates '[org.clojars.dantheman/test "0.0.3-SNAPSHOT"] + :coordinates '[fake/test "0.0.3-SNAPSHOT"] :jar-file (io/file (io/resource "test.jar")) :pom-file (io/file (io/resource "test-0.0.3-SNAPSHOT/test.pom")) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") @@ -161,12 +160,47 @@ (aether/deploy :coordinates '[org.clojars.dantheman/test.thing "0.0.3-SNAPSHOT"] :jar-file (io/file (io/resource "test.jar")) - :pom-file (io/file (io/resource "test-0.0.3-SNAPSHOT/test.pom")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.3-SNAPSHOT/test.pom")) + {:groupId "org.clojars.dantheman" + :artifactId "test.thing"}) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") :username "dantheman" :password "password"}} :local-repo help/local-repo)) +(deftest user-can-deploy-with-signatures + (-> (session (help/app-from-system)) + (register-as "dantheman" "test@example.org" "password")) + (let [pom (io/file (io/resource "test-0.0.1/test.pom"))] + (aether/deploy + :coordinates '[fake/test "0.0.1"] + :artifact-map {[:extension "jar"] (io/file (io/resource "test.jar")) + [:extension "pom"] pom + ;; any content will do since we don't validate signatures + [:extension "jar.asc"] pom + [:extension "pom.asc"] pom} + :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") + :username "dantheman" + :password "password"}} + :local-repo help/local-repo))) + +(deftest missing-signature-fails-the-deploy + (-> (session (help/app-from-system)) + (register-as "dantheman" "test@example.org" "password")) + (let [pom (io/file (io/resource "test-0.0.1/test.pom"))] + (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException + #"test-0.0.1.pom has no signature" + (aether/deploy + :coordinates '[fake/test "0.0.1"] + :artifact-map {[:extension "jar"] (io/file (io/resource "test.jar")) + [:extension "pom"] pom + ;; any content will do since we don't validate signatures + [:extension "jar.asc"] pom} + :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") + :username "dantheman" + :password "password"}} + :local-repo help/local-repo))))) + (deftest anonymous-cannot-deploy (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException #"Unauthorized" @@ -188,13 +222,33 @@ :password "password"}} :local-repo help/local-repo)))) -(deftest deploy-requires-lowercase-group +(deftest deploy-requires-path-to-match-pom (-> (session (help/app-from-system)) - (register-as "dantheman" "test@example.org" "password")) + (register-as "dantheman" "test@example.org" "password")) (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException - #"Forbidden - group names must consist solely of lowercase" + #"Forbidden - the group in the pom \(fake\) does not match the group you are deploying to \(flake\)" + (aether/deploy + :coordinates '[flake/test "0.0.1"] + :jar-file (io/file (io/resource "test.jar")) + :pom-file (io/file (io/resource "test-0.0.1/test.pom")) + :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") + :username "dantheman" + :password "password"}} + :local-repo help/local-repo))) + (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException + #"Forbidden - the name in the pom \(test\) does not match the name you are deploying to \(toast\)" (aether/deploy - :coordinates '[faKE/test "1.0.0"] + :coordinates '[fake/toast "0.0.1"] + :jar-file (io/file (io/resource "test.jar")) + :pom-file (io/file (io/resource "test-0.0.1/test.pom")) + :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") + :username "dantheman" + :password "password"}} + :local-repo help/local-repo))) + (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException + #"Forbidden - the version in the pom \(0.0.1\) does not match the version you are deploying to \(1.0.0\)" + (aether/deploy + :coordinates '[fake/test "1.0.0"] :jar-file (io/file (io/resource "test.jar")) :pom-file (io/file (io/resource "test-0.0.1/test.pom")) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") @@ -202,19 +256,35 @@ :password "password"}} :local-repo help/local-repo)))) -(deftest deploy-requires-lowercase-project +(deftest deploy-requires-lowercase-group (-> (session (help/app-from-system)) (register-as "dantheman" "test@example.org" "password")) (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException - #"Forbidden - project names must consist solely of lowercase" + #"Forbidden - group names must consist solely of lowercase" (aether/deploy - :coordinates '[fake/teST "1.0.0"] + :coordinates '[faKE/test "0.0.1"] :jar-file (io/file (io/resource "test.jar")) - :pom-file (io/file (io/resource "test-0.0.1/test.pom")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:groupId "faKE"}) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") :username "dantheman" :password "password"}} - :local-repo help/local-repo)))) + :local-repo help/local-repo))) + + (deftest deploy-requires-lowercase-project + (-> (session (help/app-from-system)) + (register-as "dantheman" "test@example.org" "password")) + (is (thrown-with-msg? org.sonatype.aether.deployment.DeploymentException + #"Forbidden - project names must consist solely of lowercase" + (aether/deploy + :coordinates '[fake/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")) + {:artifactId "teST"}) + :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") + :username "dantheman" + :password "password"}} + :local-repo help/local-repo))))) (deftest deploy-requires-ascii-version (-> (session (help/app-from-system)) @@ -224,7 +294,8 @@ (aether/deploy :coordinates '[fake/test "1.α.0"] :jar-file (io/file (io/resource "test.jar")) - :pom-file (io/file (io/resource "test-0.0.1/test.pom")) + :pom-file (help/rewrite-pom (io/file (io/resource "test-0.0.1/test.pom")) + {:version "1.α.0"}) :repository {"test" {:url (str "http://localhost:" help/test-port "/repo") :username "dantheman" :password "password"}} diff --git a/test/clojars/test/test_helper.clj b/test/clojars/test/test_helper.clj index 369cdb9d..e4e34d53 100644 --- a/test/clojars/test/test_helper.clj +++ b/test/clojars/test/test_helper.clj @@ -12,7 +12,7 @@ [clojure.java [io :as io] [jdbc :as jdbc]] - [clojure.string :as string] + [clojure.string :as str] [clucy.core :as clucy] [com.stuartsierra.component :as component]) (:import java.io.File)) @@ -107,9 +107,22 @@ (component/stop system)))))))) (defn get-content-type [resp] - (some-> resp :headers (get "content-type") (string/split #";") first)) + (some-> resp :headers (get "content-type") (str/split #";") first)) (defn assert-cors-header [resp] (some-> resp :headers (get "access-control-allow-origin") (= "*"))) + +(defn rewrite-pom [file m] + (let [new-pom (doto (File/createTempFile (.getName file) ".pom") + .deleteOnExit)] + (-> file + slurp + (as-> % (reduce (fn [accum [element new-value]] + (str/replace accum (re-pattern (format "<(%s)>.*?<" (name element))) + (format "<$1>%s<" new-value))) + % + m)) + (->> (spit new-pom))) + new-pom))