-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathauthorize.go
147 lines (133 loc) · 4.65 KB
/
authorize.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main
import (
"crypto/sha256"
"fmt"
"net/http"
"regexp"
"time"
log "github.com/sirupsen/logrus"
"go.mozilla.org/hawk"
)
// an authorization
type authorization struct {
ID string
Key string
Signers []string
}
// authIDFormat is a regex for the format Authorization IDs must follow
const authIDFormat = `^[a-zA-Z0-9-_]{1,255}$`
var authIDFormatRegexp = regexp.MustCompile(authIDFormat)
func abs(d time.Duration) time.Duration {
if d < 0 {
return -d
}
return d
}
// authorizeHeader validates the existence of the Authorization header as a hawk
// authorization header and makes sure that it is valid. It does not validate the
// body of the request, that is done within authorizeBody. This function returns
// the hawk auth struct, the userid, and an error which will indicate whether
// validation was successful.
func (a *autographer) authorizeHeader(r *http.Request) (auth *hawk.Auth, userid string, err error) {
if r.Header.Get("Authorization") == "" {
return nil, "", fmt.Errorf("missing Authorization header")
}
auth, err = hawk.ParseRequestHeader(r.Header.Get("Authorization"))
sendStatsErr := a.stats.Timing("hawk.header_parsed", time.Since(getRequestStartTime(r)), nil, 1.0)
if sendStatsErr != nil {
log.Warnf("Error sending hawk.header_parsed: %s", sendStatsErr)
}
if err != nil {
return nil, "", err
}
userid = auth.Credentials.ID
auth, err = hawk.NewAuthFromRequest(r, a.lookupCred(userid), a.lookupNonce)
sendStatsErr = a.stats.Timing("hawk.auth_created", time.Since(getRequestStartTime(r)), nil, 1.0)
if sendStatsErr != nil {
log.Warnf("Error sending hawk.auth_created: %s", sendStatsErr)
}
if err != nil {
return nil, "", err
}
_, err = a.getAuthByID(userid)
if err != nil {
return nil, "", fmt.Errorf("error finding auth for id %s for hawk.MaxTimestampSkew: %w", userid, err)
}
hawk.MaxTimestampSkew = a.hawkMaxTimestampSkew
err = auth.Valid()
sendStatsErr = a.stats.Timing("hawk.validated", time.Since(getRequestStartTime(r)), nil, 1.0)
if sendStatsErr != nil {
log.Warnf("Error sending hawk.validated: %s", sendStatsErr)
}
skew := abs(auth.ActualTimestamp.Sub(auth.Timestamp))
sendStatsErr = a.stats.Timing("hawk.timestamp_skew", skew, nil, 1.0)
if sendStatsErr != nil {
log.Warnf("Error sending hawk.timestamp_skew: %s", sendStatsErr)
}
if err != nil {
return nil, "", err
}
return auth, userid, nil
}
// authorizeBody validates the body within the request and returns
// an error which will be nil if the authorization is successful
func (a *autographer) authorizeBody(auth *hawk.Auth, r *http.Request, body []byte) (err error) {
payloadhash := auth.PayloadHash(r.Header.Get("Content-Type"))
payloadhash.Write(body)
sendStatsErr := a.stats.Timing("hawk.payload_hashed", time.Since(getRequestStartTime(r)), nil, 1.0)
if sendStatsErr != nil {
log.Warnf("Error sending hawk.payload_hashed: %s", sendStatsErr)
}
if !auth.ValidHash(payloadhash) {
return fmt.Errorf("payload validation failed")
}
return nil
}
// authorize combines authorizeHeader and authorizeBody into one function.
func (a *autographer) authorize(r *http.Request, body []byte) (userid string, err error) {
auth, userid, err := a.authorizeHeader(r)
if err != nil {
return userid, err
}
err = a.authorizeBody(auth, r, body)
return userid, err
}
// lookupCred searches the authorizations for a user whose id matches the provided
// id string. If found, a Credential function is return to complete the hawk authorization.
// If not found, a function that returns an error is returned.
func (a *autographer) lookupCred(id string) hawk.CredentialsLookupFunc {
auth, err := a.getAuthByID(id)
if err == nil {
// matching user found, return its token
return func(creds *hawk.Credentials) error {
creds.Key = auth.Key
creds.Hash = sha256.New
return nil
}
}
// credentials not found or other error, return a function that returns a CredentialError
return func(creds *hawk.Credentials) error {
return &hawk.CredentialError{
Type: hawk.UnknownID,
Credentials: &hawk.Credentials{
ID: id,
Key: "-",
Hash: sha256.New,
},
}
}
}
// lookupNonce searches the LRU cache for a previous nonce that matches the value provided in
// val. If found, this is a replay attack, and `false` is returned.
func (a *autographer) lookupNonce(val string, ts time.Time, creds *hawk.Credentials) bool {
if a.nonces.Contains(val) {
return false
}
a.nonces.Add(val, time.Now())
return true
}