pax_global_header00006660000000000000000000000064131350744530014517gustar00rootroot0000000000000052 comment=e23a45678fab6fb659d3edf6f8d8628e60e8539d ring-defaults-0.3.1/000077500000000000000000000000001313507445300142645ustar00rootroot00000000000000ring-defaults-0.3.1/.gitignore000066400000000000000000000001241313507445300162510ustar00rootroot00000000000000/target /classes /checkouts pom.xml pom.xml.asc *.jar *.class /.lein-* /.nrepl-port ring-defaults-0.3.1/.travis.yml000066400000000000000000000000501313507445300163700ustar00rootroot00000000000000language: clojure script: lein test-all ring-defaults-0.3.1/README.md000066400000000000000000000136731313507445300155550ustar00rootroot00000000000000# Ring-Defaults [![Build Status](https://travis-ci.org/ring-clojure/ring-defaults.svg?branch=master)](https://travis-ci.org/ring-clojure/ring-defaults) Knowing what middleware to add to a Ring application, and in what order, can be difficult and prone to error. This library attempts to automate the process, by providing sensible and secure default configurations of Ring middleware for both websites and HTTP APIs. ## Installation Add the following dependency to your `project.clj`: [ring/ring-defaults "0.3.1"] ## Basic Usage The `wrap-defaults` middleware sets up standard Ring middleware based on a supplied configuration: ```clojure (require '[ring.middleware.defaults :refer :all]) (def site (wrap-defaults handler site-defaults)) ``` There are four configurations included with the middleware - `api-defaults` - `site-defaults` - `secure-api-defaults` - `secure-site-defaults` The "api" defaults will add support for urlencoded parameters, but not much else. The "site" defaults add support for parameters, cookies, sessions, static resources, file uploads, and a bunch of browser-specific security headers. The "secure" defaults force SSL. Unencrypted HTTP URLs are redirected to the equivlant HTTPS URL, and various headers and flags are sent to prevent the browser sending sensitive information over insecure channels. ## Proxies If your app is sitting behind a load balancer or reverse proxy, as is often the case in cloud-based deployments, you'll want to set `:proxy` to `true`: ```clojure (assoc secure-site-defaults :proxy true) ``` This is particularly important when your site is secured with SSL, as the SSL redirect middleware will get caught in a redirect loop if it can't determine the correct URL scheme of the request. ## Customizing The default configurations are just maps of options, and can be customized to suit your needs. For example, if you wanted the normal site defaults, but without session support, you could use: ```clojure (wrap-defaults handler (assoc site-defaults :session false)) ``` The following configuration keys are supported: - `:cookies` - Set to true to parse cookies from the request. - `:params` - A map of options that describes how to parse parameters from the request. - `:keywordize` - Set to true to turn the parameter keys into keywords. - `:multipart` - Set to true to parse urlencoded parameters in the query string and the request body, or supply a map of options to pass to the standard Ring [multipart-params][1] middleware. - `:nested` - Set to true to allow nested parameters via the standard Ring [nested-params][2] middleware - `:urlencoded` - Set to true to parse urlencoded parameters in the query string and the request body. - `:proxy` - Set to true if the application is running behind a reverse proxy or load balancer. - `:responses` - A map of options to augment the responses from your application. - `:absolute-redirects` - Any redirects to relative URLs will be turned into redirects to absolute URLs, to better conform to the HTTP spec. - `:content-types` - Adds the standard Ring [content-type][3] middleware. - `:default-charset` - Adds a default charset to any text content-type lacking a charset. - `:not-modified-responses` - Adds the standard Ring [not-modified][4] middleware. - `:security` - Options for security related behaviors and headers. - `:anti-forgery` - Set to true to add CSRF protection via the [ring-anti-forgery][5] library. - `:content-type-options` - Prevents attacks based around media-type confusion. See: [wrap-content-type-options][6]. - `:frame-options` - Prevents your site from being placed in frames or iframes. See: [wrap-frame-options][7]. - `:hsts` - If true, enable HTTP Strict Transport Security. See: [wrap-hsts][8]. - `:ssl-redirect` - If true, redirect all HTTP requests to the equivalent HTTPS URL. A map with an `:ssl-port` option may be set instead, if the HTTPS server is on a non-standard port. See: [wrap-ssl-redirect][9]. - `:xss-protection` - Enable the X-XSS-Protection header that tells supporting browsers to use heuristics to detect XSS attacks. See: [wrap-xss-protection][10]. - `:session` - A map of options for configuring session handling via the Ring [session][11] middleware. - `:flash` - If set to true, the Ring [flash][12] middleware is added. - `:store` - The Ring session store to use for storing sessions. - `:static` A map of options to configure how to find static content. - `:files` - A string or collection of strings containing paths to directories to serve files from. Usually the `:resources` option below is more useful. - `:resources` - A string or collection of strings containing classpath prefixes. This will serve any resources in locations starting with the supplied prefix. [1]: https://ring-clojure.github.io/ring/ring.middleware.multipart-params.html [2]: https://ring-clojure.github.io/ring/ring.middleware.nested-params.html [3]: https://ring-clojure.github.io/ring/ring.middleware.content-type.html [4]: https://ring-clojure.github.io/ring/ring.middleware.not-modified.html [5]: https://github.com/ring-clojure/ring-anti-forgery [6]: https://ring-clojure.github.io/ring-headers/ring.middleware.x-headers.html#var-wrap-content-type-options [7]: https://ring-clojure.github.io/ring-headers/ring.middleware.x-headers.html#var-wrap-frame-options [8]: https://ring-clojure.github.io/ring-ssl/ring.middleware.ssl.html#var-wrap-hsts [9]: https://ring-clojure.github.io/ring-ssl/ring.middleware.ssl.html#var-wrap-ssl-redirect [10]: https://ring-clojure.github.io/ring-headers/ring.middleware.x-headers.html#var-wrap-xss-protection [11]: https://ring-clojure.github.io/ring/ring.middleware.session.html [12]: https://ring-clojure.github.io/ring/ring.middleware.flash.html ## License Copyright © 2017 James Reeves Distributed under the MIT License, the same as Ring. ring-defaults-0.3.1/project.clj000066400000000000000000000016161313507445300164300ustar00rootroot00000000000000(defproject ring/ring-defaults "0.3.1" :description "Ring middleware that provides sensible defaults" :url "https://github.com/ring-clojure/ring-defaults" :license {:name "The MIT License" :url "http://opensource.org/licenses/MIT"} :dependencies [[org.clojure/clojure "1.5.1"] [ring/ring-core "1.6.0"] [ring/ring-ssl "0.3.0"] [ring/ring-headers "0.3.0"] [ring/ring-anti-forgery "1.1.0"] [javax.servlet/javax.servlet-api "3.1.0"]] :aliases {"test-all" ["with-profile" "default:+1.6:+1.7:+1.8:+1.9" "test"]} :profiles {:dev {:dependencies [[ring/ring-mock "0.3.0"]]} :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} :1.9 {:dependencies [[org.clojure/clojure "1.9.0-alpha17"]]}}) ring-defaults-0.3.1/src/000077500000000000000000000000001313507445300150535ustar00rootroot00000000000000ring-defaults-0.3.1/src/ring/000077500000000000000000000000001313507445300160125ustar00rootroot00000000000000ring-defaults-0.3.1/src/ring/middleware/000077500000000000000000000000001313507445300201275ustar00rootroot00000000000000ring-defaults-0.3.1/src/ring/middleware/defaults.clj000066400000000000000000000124101313507445300224260ustar00rootroot00000000000000(ns ring.middleware.defaults "Middleware for providing a handler with sensible defaults." (:require [ring.middleware.x-headers :as x] [ring.middleware.flash :refer [wrap-flash]] [ring.middleware.session :refer [wrap-session]] [ring.middleware.keyword-params :refer [wrap-keyword-params]] [ring.middleware.nested-params :refer [wrap-nested-params]] [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] [ring.middleware.multipart-params :refer [wrap-multipart-params]] [ring.middleware.params :refer [wrap-params]] [ring.middleware.cookies :refer [wrap-cookies]] [ring.middleware.resource :refer [wrap-resource]] [ring.middleware.file :refer [wrap-file]] [ring.middleware.not-modified :refer [wrap-not-modified]] [ring.middleware.content-type :refer [wrap-content-type]] [ring.middleware.default-charset :refer [wrap-default-charset]] [ring.middleware.absolute-redirects :refer [wrap-absolute-redirects]] [ring.middleware.ssl :refer [wrap-ssl-redirect wrap-hsts wrap-forwarded-scheme]] [ring.middleware.proxy-headers :refer [wrap-forwarded-remote-addr]])) (def api-defaults "A default configuration for a HTTP API." {:params {:urlencoded true :keywordize true} :responses {:not-modified-responses true :absolute-redirects true :content-types true :default-charset "utf-8"}}) (def secure-api-defaults "A default configuration for a HTTP API that's accessed securely over HTTPS." (-> api-defaults (assoc-in [:security :ssl-redirect] true) (assoc-in [:security :hsts] true))) (def site-defaults "A default configuration for a browser-accessible website, based on current best practice." {:params {:urlencoded true :multipart true :nested true :keywordize true} :cookies true :session {:flash true :cookie-attrs {:http-only true, :same-site :strict}} :security {:anti-forgery true :xss-protection {:enable? true, :mode :block} :frame-options :sameorigin :content-type-options :nosniff} :static {:resources "public"} :responses {:not-modified-responses true :absolute-redirects true :content-types true :default-charset "utf-8"}}) (def secure-site-defaults "A default configuration for a browser-accessible website that's accessed securely over HTTPS." (-> site-defaults (assoc-in [:session :cookie-attrs :secure] true) (assoc-in [:session :cookie-name] "secure-ring-session") (assoc-in [:security :ssl-redirect] true) (assoc-in [:security :hsts] true))) (defn- wrap [handler middleware options] (if (true? options) (middleware handler) (if options (middleware handler options) handler))) (defn- wrap-multi [handler middleware args] (wrap handler (fn [handler args] (if (coll? args) (reduce middleware handler args) (middleware handler args))) args)) (defn- wrap-xss-protection [handler options] (x/wrap-xss-protection handler (:enable? options true) (dissoc options :enable?))) (defn- wrap-x-headers [handler options] (-> handler (wrap wrap-xss-protection (:xss-protection options false)) (wrap x/wrap-frame-options (:frame-options options false)) (wrap x/wrap-content-type-options (:content-type-options options false)))) (defn wrap-defaults "Wraps a handler in default Ring middleware, as specified by the supplied configuration map. See: api-defaults site-defaults secure-api-defaults secure-site-defaults" [handler config] (-> handler (wrap wrap-anti-forgery (get-in config [:security :anti-forgery] false)) (wrap wrap-flash (get-in config [:session :flash] false)) (wrap wrap-session (:session config false)) (wrap wrap-keyword-params (get-in config [:params :keywordize] false)) (wrap wrap-nested-params (get-in config [:params :nested] false)) (wrap wrap-multipart-params (get-in config [:params :multipart] false)) (wrap wrap-params (get-in config [:params :urlencoded] false)) (wrap wrap-cookies (get-in config [:cookies] false)) (wrap wrap-absolute-redirects (get-in config [:responses :absolute-redirects] false)) (wrap-multi wrap-resource (get-in config [:static :resources] false)) (wrap-multi wrap-file (get-in config [:static :files] false)) (wrap wrap-content-type (get-in config [:responses :content-types] false)) (wrap wrap-default-charset (get-in config [:responses :default-charset] false)) (wrap wrap-not-modified (get-in config [:responses :not-modified-responses] false)) (wrap wrap-x-headers (:security config)) (wrap wrap-hsts (get-in config [:security :hsts] false)) (wrap wrap-ssl-redirect (get-in config [:security :ssl-redirect] false)) (wrap wrap-forwarded-scheme (boolean (:proxy config))) (wrap wrap-forwarded-remote-addr (boolean (:proxy config))))) ring-defaults-0.3.1/test/000077500000000000000000000000001313507445300152435ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/000077500000000000000000000000001313507445300162025ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/assets/000077500000000000000000000000001313507445300175045ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/assets/public1/000077500000000000000000000000001313507445300210435ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/assets/public1/foo.txt000066400000000000000000000000051313507445300223620ustar00rootroot00000000000000foo1 ring-defaults-0.3.1/test/ring/assets/public2/000077500000000000000000000000001313507445300210445ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/assets/public2/bar.txt000066400000000000000000000000041313507445300223430ustar00rootroot00000000000000bar ring-defaults-0.3.1/test/ring/assets/public2/foo.txt000066400000000000000000000000051313507445300223630ustar00rootroot00000000000000foo2 ring-defaults-0.3.1/test/ring/middleware/000077500000000000000000000000001313507445300203175ustar00rootroot00000000000000ring-defaults-0.3.1/test/ring/middleware/defaults_test.clj000066400000000000000000000173021313507445300236620ustar00rootroot00000000000000(ns ring.middleware.defaults-test (:require [clojure.test :refer :all] [ring.middleware.defaults :refer :all] [ring.util.response :refer [response content-type]] [ring.mock.request :refer [request header]])) (deftest test-wrap-defaults (testing "api defaults" (let [handler (-> (constantly (response "foo")) (wrap-defaults api-defaults)) resp (handler (request :get "/"))] (is (= resp {:status 200 :headers {"Content-Type" "application/octet-stream"} :body "foo"})))) (testing "site defaults" (let [handler (-> (constantly (response "foo")) (wrap-defaults site-defaults)) resp (handler (request :get "/"))] (is (= (:status resp) 200)) (is (= (:body resp) "foo")) (is (= (set (keys (:headers resp))) #{"X-Frame-Options" "X-Content-Type-Options" "X-XSS-Protection" "Content-Type" "Set-Cookie"})) (is (= (get-in resp [:headers "X-Frame-Options"]) "SAMEORIGIN")) (is (= (get-in resp [:headers "X-Content-Type-Options"]) "nosniff")) (is (= (get-in resp [:headers "X-XSS-Protection"]) "1; mode=block")) (is (= (get-in resp [:headers "Content-Type"]) "application/octet-stream")) (let [set-cookie (first (get-in resp [:headers "Set-Cookie"]))] (is (.startsWith set-cookie "ring-session=")) (is (.contains set-cookie "HttpOnly")) (is (.contains set-cookie "SameSite=Strict"))))) (testing "default charset" (let [handler (-> (constantly (-> (response "foo") (content-type "text/plain"))) (wrap-defaults site-defaults)) resp (handler (request :get "/"))] (is (= (get-in resp [:headers "Content-Type"]) "text/plain; charset=utf-8")))) (testing "middleware overrides" (let [handler (-> (constantly (response "foo")) (wrap-defaults (assoc-in site-defaults [:security :frame-options] :deny))) resp (handler (request :get "/"))] (is (= (get-in resp [:headers "X-Frame-Options"]) "DENY")) (is (= (get-in resp [:headers "X-Content-Type-Options"]) "nosniff")))) (testing "disabled middleware" (let [handler (-> (constantly (response "foo")) (wrap-defaults (assoc-in site-defaults [:security :frame-options] false))) resp (handler (request :get "/"))] (is (nil? (get-in resp [:headers "X-Frame-Options"]))) (is (= (get-in resp [:headers "X-Content-Type-Options"]) "nosniff")))) (testing "ssl redirect (site)" (let [handler (-> (constantly (response "foo")) (wrap-defaults secure-site-defaults)) resp (handler (request :get "/foo"))] (is (= resp {:status 301 :headers {"Location" "https://localhost/foo"} :body ""})))) (testing "ssl redirect (api)" (let [handler (-> (constantly (response "foo")) (wrap-defaults secure-api-defaults)) resp (handler (request :get "/foo"))] (is (= resp {:status 301 :headers {"Location" "https://localhost/foo"} :body ""})))) (testing "ssl proxy redirect" (let [handler (-> (constantly (response "foo")) (wrap-defaults (assoc secure-site-defaults :proxy true))) resp (handler (-> (request :get "/foo") (header "x-forwarded-proto" "https")))] (is (= (:status resp) 200)) (is (= (:body resp) "foo")))) (testing "secure api defaults" (let [handler (-> (constantly (response "foo")) (wrap-defaults secure-api-defaults)) resp (handler (request :get "https://localhost/foo"))] (is (= resp {:status 200 :headers {"Content-Type" "application/octet-stream" "Strict-Transport-Security" "max-age=31536000; includeSubDomains"} :body "foo"})))) (testing "secure site defaults" (let [handler (-> (constantly (response "foo")) (wrap-defaults secure-site-defaults)) resp (handler (request :get "https://localhost/"))] (is (= (:status resp) 200)) (is (= (:body resp) "foo")) (is (= (set (keys (:headers resp))) #{"X-Frame-Options" "X-Content-Type-Options" "X-XSS-Protection" "Strict-Transport-Security" "Content-Type" "Set-Cookie"})) (is (= (get-in resp [:headers "X-Frame-Options"]) "SAMEORIGIN")) (is (= (get-in resp [:headers "X-Content-Type-Options"]) "nosniff")) (is (= (get-in resp [:headers "X-XSS-Protection"]) "1; mode=block")) (is (= (get-in resp [:headers "Strict-Transport-Security"]) "max-age=31536000; includeSubDomains")) (is (= (get-in resp [:headers "Content-Type"]) "application/octet-stream")) (let [set-cookie (first (get-in resp [:headers "Set-Cookie"]))] (is (.startsWith set-cookie "secure-ring-session=")) (is (.contains set-cookie "HttpOnly")) (is (.contains set-cookie "Secure"))))) (testing "proxy headers" (let [handler (wrap-defaults response {:proxy true}) resp (handler (-> (request :get "/") (header "x-forwarded-proto" "https") (header "x-forwarded-for" "10.0.0.1, 1.2.3.4"))) body (:body resp)] (is (= (:scheme body) :https)) (is (= (:remote-addr body) "1.2.3.4")))) (testing "nil response" (let [handler (wrap-defaults (constantly nil) site-defaults)] (is (nil? (handler (request :get "/")))))) (testing "single resource path" (let [handler (wrap-defaults (constantly nil) (assoc-in site-defaults [:static :resources] "ring/assets/public1"))] (is (= (slurp (:body (handler (request :get "/foo.txt")))) "foo1\n")) (is (nil? (handler (request :get "/bar.txt")))))) (testing "multiple resource paths" (let [handler (wrap-defaults (constantly nil) (assoc-in site-defaults [:static :resources] ["ring/assets/public1" "ring/assets/public2"]))] (is (= (slurp (:body (handler (request :get "/foo.txt")))) "foo2\n")) (is (= (slurp (:body (handler (request :get "/bar.txt")))) "bar\n")))) (testing "single file path" (let [handler (wrap-defaults (constantly nil) (assoc-in site-defaults [:static :files] "test/ring/assets/public1"))] (is (= (slurp (:body (handler (request :get "/foo.txt")))) "foo1\n")) (is (nil? (handler (request :get "/bar.txt")))))) (testing "multiple file paths" (let [handler (wrap-defaults (constantly nil) (assoc-in site-defaults [:static :files] ["test/ring/assets/public1" "test/ring/assets/public2"]))] (is (= (slurp (:body (handler (request :get "/foo.txt")))) "foo2\n")) (is (= (slurp (:body (handler (request :get "/bar.txt")))) "bar\n")))) (testing "async handlers" (let [handler (-> (fn [_ respond _] (respond (response "foo"))) (wrap-defaults api-defaults)) resp (promise) ex (promise)] (handler (request :get "/") resp ex) (is (not (realized? ex))) (is (= @resp {:status 200 :headers {"Content-Type" "application/octet-stream"} :body "foo"})))))