Skip to content

Commit 41af011

Browse files
committed
Add support for Authorization header
Add the wrap-authorization middleware for parsing the Authorization header on the request map. This is not a complete authentication system; it only parses the auth token and parameters. See RFC 7235 and RFC 9110: * https://datatracker.ietf.org/doc/html/rfc7235 * https://datatracker.ietf.org/doc/html/rfc9110#section-11 This change raises the dependency on ring-core to 1.8.1, and drops support for Clojure 1.5 and 1.6.
1 parent 3c803eb commit 41af011

4 files changed

Lines changed: 151 additions & 5 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
[![Build Status](https://travis-ci.org/ring-clojure/ring-headers.svg?branch=master)](https://travis-ci.org/ring-clojure/ring-headers)
44

5-
Ring middleware for adding and manipulating common response headers.
5+
Ring middleware for parsing common request headers, and for adding and
6+
manipulating common response headers.
67

78
## Installation
89

project.clj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
:url "https://github.com/ring-clojure/ring-headers"
44
:license {:name "The MIT License"
55
:url "http://opensource.org/licenses/MIT"}
6-
:dependencies [[org.clojure/clojure "1.5.1"]
7-
[ring/ring-core "1.6.0"]]
6+
:dependencies [[org.clojure/clojure "1.7.0"]
7+
[ring/ring-core "1.8.1"]]
88
:plugins [[lein-codox "0.10.3"]]
99
:codox {:project {:name "Ring-Headers"}
1010
:output-path "codox"}
11-
:aliases {"test-all" ["with-profile" "default:+1.6:+1.7:+1.8:+1.9" "test"]}
11+
:aliases {"test-all" ["with-profile" "default:+1.7:+1.8:+1.9" "test"]}
1212
:profiles
1313
{:dev {:dependencies [[ring/ring-mock "0.3.0"]]}
14-
:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]}
1514
:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}
1615
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
1716
:1.9 {:dependencies [[org.clojure/clojure "1.9.0-alpha10"]]}})
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
(ns ring.middleware.authorization
2+
(:require [clojure.string :as str]
3+
[ring.util.parsing :as parsing]))
4+
5+
(def ^:private re-token68
6+
(re-pattern "[A-Za-z0-9._~+/-]+=*"))
7+
8+
(def ^:private re-auth-param
9+
(re-pattern (str "(" parsing/re-token ")\\s*=\\s*(?:" parsing/re-value ")")))
10+
11+
(defn- parse-auth-params [auth-params]
12+
(reduce (fn [m kv]
13+
(if-let [[_ k v1 v2] (re-matches re-auth-param kv)]
14+
(assoc m (str/lower-case k) (or v1 v2))
15+
m))
16+
{}
17+
(str/split auth-params #"\s*,\s*")))
18+
19+
(defn- parse-authorization [request]
20+
(when-let [[auth-scheme token-or-params]
21+
(some-> (get-in request [:headers "authorization"])
22+
(str/split #"\s" 2))]
23+
(cond
24+
(empty? token-or-params)
25+
{:scheme (str/lower-case auth-scheme)}
26+
27+
(re-matches re-token68 token-or-params)
28+
{:scheme (str/lower-case auth-scheme)
29+
:token token-or-params}
30+
31+
:else
32+
{:scheme (str/lower-case auth-scheme)
33+
:params (parse-auth-params token-or-params)})))
34+
35+
(defn authorization-request
36+
"Parses Authorization header in the request map. See: wrap-authorization."
37+
[request]
38+
(if (:authorization request)
39+
request
40+
(assoc request :authorization (parse-authorization request))))
41+
42+
(defn wrap-authorization
43+
"Parses the Authorization header in the request map, then assocs the result
44+
to the :authorization key on the request.
45+
46+
See RFC 7235 Section 2 and RFC 9110 Section 11:
47+
* https://datatracker.ietf.org/doc/html/rfc7235#section-2
48+
* https://datatracker.ietf.org/doc/html/rfc9110#section-11"
49+
[handler]
50+
(fn
51+
([request]
52+
(handler (authorization-request request)))
53+
([request respond raise]
54+
(handler (authorization-request request)
55+
respond
56+
raise))))
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
(ns ring.middleware.authorization-test
2+
(:require [clojure.test :refer :all]
3+
[ring.middleware.authorization :refer :all]))
4+
5+
(deftest test-authorization-request
6+
(testing "pre-existing authorization"
7+
(is (= "TEST"
8+
(-> {:headers {"authorization" "Basic"}
9+
:authorization "TEST"}
10+
authorization-request
11+
:authorization))))
12+
(testing "no authorization"
13+
(is (nil? (-> {:headers {}}
14+
authorization-request
15+
:authorization))))
16+
(testing "scheme without token"
17+
(is (= {:scheme "basic"}
18+
(-> {:headers {"authorization" "Basic"}}
19+
authorization-request
20+
:authorization))))
21+
(testing "scheme with zero-length token"
22+
(is (= {:scheme "basic"}
23+
(-> {:headers {"authorization" "Basic "}}
24+
authorization-request
25+
:authorization))))
26+
(testing "token68"
27+
(is (= {:scheme "basic"
28+
:token "dGVzdA=="}
29+
(-> {:headers {"authorization" "Basic dGVzdA=="}}
30+
authorization-request
31+
:authorization))))
32+
(testing "auth-params, some malformed"
33+
(is (= {:scheme "digest"
34+
:params {"a" "B"
35+
"c" "d"
36+
"eeee" "dGVzdA=="
37+
"k" "1"}}
38+
(-> {:headers {"authorization" "Digest A=B, c=\"d\",
39+
eeee=\"dGVzdA==\", fparam=dGVzdA==, g, \"h\"=i, =j, = ,, , k=1"}}
40+
authorization-request
41+
:authorization)))))
42+
43+
(deftest test-wrap-authorization-none
44+
(let [handler (wrap-authorization (fn [req respond _] (respond req)))
45+
request {:headers {}}
46+
response (promise)
47+
exception (promise)]
48+
(handler request response exception)
49+
(is (nil? (:authorization @response)))
50+
(is (not (realized? exception)))))
51+
52+
(deftest test-wrap-authorization-scheme-only
53+
(let [handler (wrap-authorization (fn [req respond _] (respond req)))
54+
request {:headers {"authorization" "Basic"}}
55+
response (promise)
56+
exception (promise)]
57+
(handler request response exception)
58+
(is (= {:scheme "basic"}
59+
(:authorization @response)))
60+
(is (not (realized? exception)))))
61+
62+
(deftest test-wrap-authorization-token68
63+
(let [handler (wrap-authorization (fn [req respond _] (respond req)))
64+
request {:headers {"authorization" "Basic dGVzdA=="}}
65+
response (promise)
66+
exception (promise)]
67+
(handler request response exception)
68+
(is (= {:scheme "basic"
69+
:token "dGVzdA=="}
70+
(:authorization @response)))
71+
(is (not (realized? exception)))))
72+
73+
(deftest test-wrap-authorization-auth-params
74+
(let [handler (wrap-authorization (fn [req respond _] (respond req)))
75+
request {:headers {"authorization" "Digest A=\"B\""}}
76+
response (promise)
77+
exception (promise)]
78+
(handler request response exception)
79+
(is (= {:params {"a" "B"}
80+
:scheme "digest"}
81+
(:authorization @response)))
82+
(is (not (realized? exception)))))
83+
84+
(deftest test-wrap-authorization-synchronous
85+
(let [request (atom nil)
86+
handler (wrap-authorization (fn [req] (reset! request req)))]
87+
(handler {:headers {"authorization" "Basic A=\"B\""}})
88+
(is (= {:params {"a" "B"}
89+
:scheme "basic"}
90+
(:authorization @request)))))

0 commit comments

Comments
 (0)