@@ -0,0 +1,302 @@
13+ "github.com/stretchr/testify/assert"
16+// These correspond to the headers used in bitly/oauth2_proxy#147.
17+var HEADERS = []string{
25+ "X-Forwarded-Access-Token",
30+func TestSupportedHashAlgorithm(t *testing.T) {
31+ algorithm, err := DigestNameToCryptoHash("sha1")
32+ assert.Equal(t, err, nil)
33+ assert.Equal(t, algorithm, crypto.SHA1)
34+ assert.Equal(t, algorithm.Available(), true)
37+func TestUnsupportedHashAlgorithm(t *testing.T) {
38+ algorithm, err := DigestNameToCryptoHash("unsupported")
39+ assert.NotEqual(t, err, nil)
40+ assert.Equal(t, err.Error(),
41+ "hmacauth: hash algorithm not supported: unsupported")
42+ assert.Equal(t, algorithm, crypto.Hash(0))
43+ assert.Equal(t, algorithm.Available(), false)
46+func TestResultUnsupportedAlgorithmWillCauseNewHmacAuthToPanic(t *testing.T) {
50+ "hmacauth: hash algorithm #0 is unavailable")
52+ NewHmacAuth(crypto.Hash(0), nil, "", nil)
55+func newTestRequest(request ...string) (req *http.Request) {
56+ reqBuf := bufio.NewReader(
57+ strings.NewReader(strings.Join(request, "\n")))
58+ if req, err := http.ReadRequest(reqBuf); err != nil {
65+func testHmacAuth() HmacAuth {
67+ crypto.SHA1, []byte("foobar"), "GAP-Signature", HEADERS)
70+func TestRequestSignaturePost(t *testing.T) {
71+ body := `{ "hello": "world!" }`
72+ req := newTestRequest(
73+ "POST /foo/bar HTTP/1.1",
74+ "Content-Length: "+strconv.Itoa(len(body)),
75+ "Content-MD5: deadbeef",
76+ "Content-Type: application/json",
78+ "Authorization: trust me",
79+ "X-Forwarded-User: mbland",
80+ "X-Forwarded-Email: mbland@acm.org",
81+ "X-Forwarded-Access-Token: feedbead",
82+ "Cookie: foo; bar; baz=quux",
89+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
91+ strconv.Itoa(len(body)),
99+ "foo; bar; baz=quux",
103+ assert.Equal(t, h.RequestSignature(req),
104+ "sha1 K4IrVDtMCRwwW8Oms0VyZWMjXHI=")
106+ if requestBody, err := io.ReadAll(req.Body); err != nil {
109+ assert.Equal(t, string(requestBody), body)
113+func newGetRequest() *http.Request {
114+ return newTestRequest(
115+ "GET /foo/bar HTTP/1.1",
117+ "Cookie: foo; bar; baz=quux",
124+func TestRequestSignatureGetWithFullUrl(t *testing.T) {
125+ req := newTestRequest(
126+ "GET http://localhost/foo/bar?baz=quux%2Fxyzzy#plugh HTTP/1.1",
128+ "Cookie: foo; bar; baz=quux",
135+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
145+ "foo; bar; baz=quux",
147+ "/foo/bar?baz=quux%2Fxyzzy#plugh",
149+ assert.Equal(t, h.RequestSignature(req),
150+ "sha1 ih5Jce9nsltry63rR4ImNz2hdnk=")
153+func TestRequestSignatureGetWithMultipleHeadersWithTheSameName(t *testing.T) {
154+ // Just using "Cookie:" out of convenience.
155+ req := newTestRequest(
156+ "GET /foo/bar HTTP/1.1",
167+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
181+ assert.Equal(t, h.RequestSignature(req),
182+ "sha1 JlRkes1X+qq3Bgc/GcRyLos+4aI=")
185+func TestAuthenticateRequestResultNoSignature(t *testing.T) {
187+ req := newGetRequest()
188+ result, header, computed := h.AuthenticateRequest(req)
189+ assert.Equal(t, result, ResultNoSignature)
190+ assert.Equal(t, header, "")
191+ assert.Equal(t, computed, "")
194+func TestAuthenticateRequestResultInvalidFormat(t *testing.T) {
196+ req := newGetRequest()
197+ badValue := "should be algorithm and digest value"
198+ req.Header.Set("GAP-Signature", badValue)
199+ result, header, computed := h.AuthenticateRequest(req)
200+ assert.Equal(t, result, ResultInvalidFormat)
201+ assert.Equal(t, header, badValue)
202+ assert.Equal(t, computed, "")
205+func TestAuthenticateRequestResultUnsupportedAlgorithm(t *testing.T) {
207+ req := newGetRequest()
208+ validSignature := h.RequestSignature(req)
209+ components := strings.Split(validSignature, " ")
210+ signatureWithResultUnsupportedAlgorithm := "unsupported " +
212+ req.Header.Set("GAP-Signature", signatureWithResultUnsupportedAlgorithm)
213+ result, header, computed := h.AuthenticateRequest(req)
214+ assert.Equal(t, result, ResultUnsupportedAlgorithm)
215+ assert.Equal(t, header, signatureWithResultUnsupportedAlgorithm)
216+ assert.Equal(t, computed, "")
219+func TestAuthenticateRequestResultMatch(t *testing.T) {
221+ req := newGetRequest()
222+ expected := h.RequestSignature(req)
224+ result, header, computed := h.AuthenticateRequest(req)
225+ assert.Equal(t, result, ResultMatch)
226+ assert.Equal(t, header, expected)
227+ assert.Equal(t, computed, expected)
230+func TestAuthenticateRequestMismatch(t *testing.T) {
231+ foobarAuth := testHmacAuth()
232+ barbazAuth := NewHmacAuth(
233+ crypto.SHA1, []byte("barbaz"), "GAP-Signature", HEADERS)
234+ req := newGetRequest()
235+ foobarAuth.SignRequest(req)
236+ result, header, computed := barbazAuth.AuthenticateRequest(req)
237+ assert.Equal(t, result, ResultMismatch)
238+ assert.Equal(t, header, foobarAuth.RequestSignature(req))
239+ assert.Equal(t, computed, barbazAuth.RequestSignature(req))
242+type SignatureAuthenticator struct {
246+func (v *SignatureAuthenticator) Authenticate(
247+ w http.ResponseWriter, r *http.Request) {
248+ result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
250+ case ResultNoSignature:
251+ w.Write([]byte("no signature received"))
253+ w.Write([]byte("signatures match"))
254+ case ResultMismatch:
255+ w.Write([]byte("signatures do not match:" +
256+ "\n received: " + headerSig +
257+ "\n computed: " + computedSig))
259+ panic("Unknown result value: " + result.String())
263+// fakeNetConn simulates an http.Request.Body buffer that will be consumed
264+// when it is read by the hmacauth.HmacAuth if not handled properly. See:
266+// https://github.com/18F/hmacauth/pull/4
267+type fakeNetConn struct {
271+func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
272+ if bodyLen := len(fnc.reqBody); bodyLen != 0 {
273+ copy(p, fnc.reqBody)
275+ return bodyLen, io.EOF
280+func TestSendAuthenticatedPostRequestToServer(t *testing.T) {
282+ payload := `{ "hello": "world!" }`
284+ auth := NewHmacAuth(crypto.SHA1, []byte(key), "X-Test-Signature", nil)
285+ authenticator := &SignatureAuthenticator{auth: auth}
286+ upstream := httptest.NewServer(
287+ http.HandlerFunc(authenticator.Authenticate))
289+ req, err := http.NewRequest("POST", upstream.URL+"/foo/bar",
290+ io.NopCloser(&fakeNetConn{reqBody: payload}))
294+ auth.SignRequest(req)
295+ if response, err := http.DefaultClient.Do(req); err != nil {
298+ assert.Equal(t, response.StatusCode, http.StatusOK)
299+ responseBody, _ := io.ReadAll(response.Body)
300+ assert.Equal(t, "signatures match", string(responseBody))