This repository has been archived by the owner on Aug 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
uaa_client.go
147 lines (122 loc) · 3.08 KB
/
uaa_client.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
package auth
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"
)
type Metrics interface {
NewGauge(name string) func(value float64)
}
type HTTPClient interface {
Do(r *http.Request) (*http.Response, error)
}
type UAAClient struct {
httpClient HTTPClient
uaa *url.URL
client string
clientSecret string
tokenCache map[string]uaaResponse
storeLatency func(float64)
}
func NewUAAClient(
uaaAddr string,
client string,
clientSecret string,
httpClient HTTPClient,
m Metrics,
log *log.Logger,
) *UAAClient {
u, err := url.Parse(uaaAddr)
if err != nil {
log.Fatalf("failed to parse UAA addr: %s", err)
}
u.Path = "check_token"
return &UAAClient{
uaa: u,
client: client,
clientSecret: clientSecret,
httpClient: httpClient,
storeLatency: m.NewGauge("LastUAALatency"),
tokenCache: make(map[string]uaaResponse),
}
}
func (c *UAAClient) Read(token string) (Oauth2ClientContext, error) {
if token == "" {
return Oauth2ClientContext{}, errors.New("missing token")
}
uaaR, ok := c.tokenCache[token]
if ok && time.Now().Before(uaaR.ExpTime) {
return Oauth2ClientContext{
IsAdmin: c.hasDopplerScope(uaaR),
Token: token,
}, nil
}
form := url.Values{
"token": {trimBearer(token)},
}
req, err := http.NewRequest("POST", c.uaa.String(), strings.NewReader(form.Encode()))
if err != nil {
log.Printf("failed to create UAA request: %s", err)
return Oauth2ClientContext{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.client, c.clientSecret)
start := time.Now()
resp, err := c.httpClient.Do(req)
c.storeLatency(float64(time.Since(start)))
if err != nil {
log.Printf("UAA request failed: %s", err)
return Oauth2ClientContext{}, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return Oauth2ClientContext{}, fmt.Errorf("Non-200 status code received from UAA")
}
uaaR, err = c.parseResponse(resp.Body)
if err != nil {
log.Printf("failed to parse UAA response body: %s", err)
return Oauth2ClientContext{}, err
}
c.tokenCache[token] = uaaR
return Oauth2ClientContext{
IsAdmin: resp.StatusCode == http.StatusOK && c.hasDopplerScope(uaaR),
Token: token,
}, nil
}
var bearerRE = regexp.MustCompile(`(?i)^bearer\s+`)
func trimBearer(authToken string) string {
return bearerRE.ReplaceAllString(authToken, "")
}
type uaaResponse struct {
Scopes []string `json:"scope"`
Exp float64 `json:"exp"`
ExpTime time.Time `json:"-"`
}
func (c *UAAClient) hasDopplerScope(r uaaResponse) bool {
for _, scope := range r.Scopes {
if scope == "doppler.firehose" || scope == "logs.admin" {
return true
}
}
return false
}
func (c *UAAClient) parseResponse(r io.Reader) (uaaResponse, error) {
var resp uaaResponse
if err := json.NewDecoder(r).Decode(&resp); err != nil {
log.Printf("unable to decode json response from UAA: %s", err)
return uaaResponse{}, err
}
resp.ExpTime = time.Unix(0, int64(resp.Exp*1e9))
return resp, nil
}