Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(118)

Delta Between Two Patch Sets: oauth/jwt/jwt.go

Issue 6452058: code review 6452058: goauth2: adding a jwt package to support server to serv...
Left Patch Set: diff -r ae891eec7124 https://code.google.com/p/goauth2/ Created 11 years, 7 months ago
Right Patch Set: diff -r ae891eec7124 https://code.google.com/p/goauth2/ Created 11 years, 7 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | oauth/jwt/jwt_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 // Copyright 2012 The goauth2 Authors. All rights reserved. 1 // Copyright 2012 The goauth2 Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 // The jwt package provides support for creating credentials for OAuth2 service 5 // The jwt package provides support for creating credentials for OAuth2 service
6 // account requests. 6 // account requests.
7 // 7 //
8 // For examples of the package usage please see jwt_test.go. 8 // For examples of the package usage please see jwt_test.go.
9 // 9 //
10 // For info on OAuth2 service accounts please see the online documentation. 10 // For info on OAuth2 service accounts please see the online documentation.
(...skipping 13 matching lines...) Expand all
24 "errors" 24 "errors"
25 "fmt" 25 "fmt"
26 "net/http" 26 "net/http"
27 "net/url" 27 "net/url"
28 "strings" 28 "strings"
29 "time" 29 "time"
30 30
31 "code.google.com/p/goauth2/oauth" 31 "code.google.com/p/goauth2/oauth"
32 ) 32 )
33 33
34 // These are the default/standard values for this to work for Google service acc ounts.
34 const ( 35 const (
35 stdAlgorithm = "RS256" 36 stdAlgorithm = "RS256"
36 stdType = "JWT" 37 stdType = "JWT"
37 stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer" 38 stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
38 stdGrantType = "assertion" 39 stdGrantType = "assertion"
39 ) 40 )
40 41
41 var ( 42 var (
42 ErrInvalidKey = errors.New("Invalid Key") 43 ErrInvalidKey = errors.New("Invalid Key")
43 44
44 StdHeader = &Header{ 45 StdHeader = &Header{
45 Algorithm: stdAlgorithm, 46 Algorithm: stdAlgorithm,
46 Type: stdType, 47 Type: stdType,
47 } 48 }
48 ) 49 )
49 50
50 // urlEncode returns and Base64url encoded version of the input string with any 51 // urlEncode returns and Base64url encoded version of the input string with any
51 // trailing "=" stripped. 52 // trailing "=" stripped.
52 func urlEncode(b []byte) string { 53 func urlEncode(b []byte) string {
53 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") 54 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
54 } 55 }
55 56
56 // The header consists of two fields that indicate the signing algorithm and the 57 // The header consists of two fields that indicate the signing algorithm and the
57 // format of the assertion. Both fields are mandatory, and each field has only 58 // format of the assertion. Both fields are mandatory, and each field has only
58 // one value. As additional algorithms and formats are introduced, this header 59 // one value. As additional algorithms and formats are introduced, this header
59 // will change accordingly. 60 // will change accordingly.
60 // 61 //
61 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format. 62 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format.
62 type Header struct { 63 type Header struct {
63 » Algorithm string `json:"alg"` 64 » Algorithm string `json:"alg"` // encoding algorithm (usually RSA SHA256)
64 » Type string `json:"typ"` 65 » Type string `json:"typ"` // token format (usually JWT)
65 } 66 }
66 67
67 // Encode returns the Base64url encoded form of the Header. 68 // Encode returns the Base64url encoded form of the Header.
68 func (h *Header) Encode() (string, error) { 69 func (h *Header) Encode() (string, error) {
69 b, err := json.Marshal(h) 70 b, err := json.Marshal(h)
70 if err != nil { 71 if err != nil {
71 return "", err 72 return "", err
72 } 73 }
73 return urlEncode(b), nil 74 return urlEncode(b), nil
74 } 75 }
75 76
76 // The JWT claim set contains information about the JWT including the 77 // The JWT claim set contains information about the JWT including the
77 // permissions being requested (scopes), the target of the token, the issuer, 78 // permissions being requested (scopes), the target of the token, the issuer,
78 // the time the token was issued, and the lifetime of the token. Most of the 79 // the time the token was issued, and the lifetime of the token.
79 // fields are mandatory.
80 // 80 //
81 // Aud is usually https://accounts.google.com/o/oauth2/token 81 // Aud is usually https://accounts.google.com/o/oauth2/token
82 type ClaimSet struct { 82 type ClaimSet struct {
83 Iss string `json:"iss"` // email address of the client_id of the application making the access token request 83 Iss string `json:"iss"` // email address of the client_id of the application making the access token request
84 Scope string `json:"scope"` // space-delimited list of the per missions the application requests 84 Scope string `json:"scope"` // space-delimited list of the per missions the application requests
85 » Aud string `json:"aud"` // a descriptor of the intended ta rget of the assertion 85 » Aud string `json:"aud"` // descriptor of the intended targ et of the assertion
86 » Exp int64 `json:"exp"` // the expiration time of the asse rtion (maximum of 1 hour from the JWT encoding time) 86 » Exp int64 `json:"exp"` // expiration time of the assertio n (Optional; maximum of 1 hour from the JWT encoding time)
87 » Iat int64 `json:"iat"` // the time the assertion was issu ed (the time that the JWT was encoded) 87 » Iat int64 `json:"iat"` // time the assertion was issued ( Optional; time that the JWT was encoded)
88 » Prn string `json:"prn,omitempty"` // the email for which the applica tion is requesting delegated access (Optional). 88 » Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
89 expTime time.Time 89 expTime time.Time
90 iatTime time.Time 90 iatTime time.Time
91 } 91 }
92 92
93 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively. 93 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively.
94 // 94 //
95 // Note that these times have nothing to do with the expiration time for the 95 // Note that these times have nothing to do with the expiration time for the
96 // access_token returned by the server. These have to do with the lifetime of 96 // access_token returned by the server. These have to do with the lifetime of
97 // the encoded JWT. 97 // the encoded JWT.
98 // 98 //
(...skipping 29 matching lines...) Expand all
128 // Encode returns the Base64url encoded form of the Signature. The error 128 // Encode returns the Base64url encoded form of the Signature. The error
129 // returned from this function is always nil and can be safely ignored. 129 // returned from this function is always nil and can be safely ignored.
130 func (s Signature) Encode() (string, error) { 130 func (s Signature) Encode() (string, error) {
131 return urlEncode([]byte(s)), nil 131 return urlEncode([]byte(s)), nil
132 } 132 }
133 133
134 // A JWT is composed of three parts: a header, a claim set, and a signature. 134 // A JWT is composed of three parts: a header, a claim set, and a signature.
135 // The well formed and encoded JWT can then be exchanged for an access token. 135 // The well formed and encoded JWT can then be exchanged for an access token.
136 // 136 //
137 // The Token is not a JWT, but is is encoded to produce a well formed JWT. 137 // The Token is not a JWT, but is is encoded to produce a well formed JWT.
138 //
139 // When obtaining a key from the Google API console it will be downloaded in a
140 // PKCS12 encoding. To use this key you will need to convert it to a PEM file.
141 // This can be achieved on a with openssl.
142 //
143 // $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out < key.pem>
144 //
145 // The contents of this file can then be used as the Key.
138 type Token struct { 146 type Token struct {
139 » Header *Header 147 » Header *Header // if nil the StdHeader will be used
140 » ClaimSet *ClaimSet 148 » ClaimSet *ClaimSet // claim set used to construct the JWT
141 » Signature Signature 149 » Signature Signature // generally computed in Encode based on the ClaimSe t
142 » Key []byte 150 » Key []byte // PEM printable encoding of the private key
gavaletz_google 2012/08/06 17:44:47 Due to an upcoming deadline I had to give an "adva
143 pKey *rsa.PrivateKey 151 pKey *rsa.PrivateKey
144 head string 152 head string
145 claim string 153 claim string
146 sig string 154 sig string
155 }
156
157 // NewToken returns a filled in *Token based on the StdHeader, and sets the Iat
158 // and Exp times based on when the call to Assert is made.
159 func NewToken(iss, scope, aud string, key []byte) *Token {
160 c := &ClaimSet{
161 Iss: iss,
162 Scope: scope,
163 Aud: aud,
164 }
165 t := &Token{
166 Header: StdHeader,
167 ClaimSet: c,
168 Key: key,
169 }
170 return t
147 } 171 }
148 172
149 // Expired returns a boolean value letting us know if the token has expired. 173 // Expired returns a boolean value letting us know if the token has expired.
150 func (t *Token) Expired() bool { 174 func (t *Token) Expired() bool {
151 // The truth is that if the sis is empty, then it was never encoded 175 // The truth is that if the sis is empty, then it was never encoded
152 // and was never sent to the server, so you should re-encode and send it 176 // and was never sent to the server, so you should re-encode and send it
153 // anyways. 177 // anyways.
154 if t.ClaimSet.Exp == 0 { 178 if t.ClaimSet.Exp == 0 {
155 return true 179 return true
156 } 180 }
157 return t.ClaimSet.expTime.Before(time.Now()) 181 return t.ClaimSet.expTime.Before(time.Now())
158 } 182 }
159 183
160 // AssertionType returns the standard assertion type for a JWT. 184 // AssertionType returns the standard assertion type for a JWT.
161 func (t *Token) AssertionType() string { 185 func (t *Token) AssertionType() string {
162 return stdAssertionType 186 return stdAssertionType
163 } 187 }
164 188
165 // Encode constructs and signs a Token returning a JWT ready to use for 189 // Encode constructs and signs a Token returning a JWT ready to use for
166 // requesting an access token. 190 // requesting an access token.
167 func (t *Token) Encode() (string, error) { 191 func (t *Token) Encode() (string, error) {
168 var tok string 192 var tok string
169 var err error 193 var err error
194 if t.Header == nil {
195 t.Header = StdHeader
196 }
170 t.head, err = t.Header.Encode() 197 t.head, err = t.Header.Encode()
171 if err != nil { 198 if err != nil {
172 return tok, err 199 return tok, err
173 } 200 }
174 t.claim, err = t.ClaimSet.Encode() 201 t.claim, err = t.ClaimSet.Encode()
175 if err != nil { 202 if err != nil {
176 return tok, err 203 return tok, err
177 } 204 }
178 err = t.sign() 205 err = t.sign()
179 if err != nil { 206 if err != nil {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 if !ok { 249 if !ok {
223 return ErrInvalidKey 250 return ErrInvalidKey
224 } 251 }
225 return nil 252 return nil
226 } 253 }
227 254
228 // Assert obtains an *oauth.Token from the remote server by encoding and sending 255 // Assert obtains an *oauth.Token from the remote server by encoding and sending
229 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be 256 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
230 // refreshed (no refresh_token is returned with the response). Once this token 257 // refreshed (no refresh_token is returned with the response). Once this token
231 // expires call this method again to get a fresh one. 258 // expires call this method again to get a fresh one.
232 func (t *Token) Assert() (*oauth.Token, error) { 259 func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
233 var o *oauth.Token 260 var o *oauth.Token
234 » req, err := t.buildRequest() 261 » u, v, err := t.buildRequest()
235 if err != nil { 262 if err != nil {
236 return o, err 263 return o, err
237 } 264 }
238 » hc := &http.Client{} 265 » resp, err := c.PostForm(u, v)
239 » resp, err := hc.Do(req)
240 if err != nil { 266 if err != nil {
241 return o, err 267 return o, err
242 } 268 }
243 o, err = handleResponse(resp) 269 o, err = handleResponse(resp)
244 return o, err 270 return o, err
245 } 271 }
246 272
247 // BuildRequest returns and *http.Request that can be used to make an access 273 // buildRequest sets up the URL values and the proper URL string for making our
248 // token request. Per the Assertion profile in OAuth 2.0 specification, this 274 // access_token request.
249 // access token request is an HTTPs POST. 275 func (t *Token) buildRequest() (string, url.Values, error) {
250 func (t *Token) buildRequest() (*http.Request, error) { 276 » v := url.Values{}
251 j, err := t.Encode() 277 j, err := t.Encode()
252 if err != nil { 278 if err != nil {
253 » » return nil, err 279 » » return t.ClaimSet.Aud, v, err
254 » } 280 » }
255 » v := url.Values{}
256 v.Set("grant_type", stdGrantType) 281 v.Set("grant_type", stdGrantType)
257 v.Set("assertion_type", t.AssertionType()) 282 v.Set("assertion_type", t.AssertionType())
258 v.Set("assertion", j) 283 v.Set("assertion", j)
259 » u, err := url.Parse(t.ClaimSet.Aud) 284 » return t.ClaimSet.Aud, v, nil
260 » if err != nil {
261 » » return nil, err
262 » }
263 » u.RawQuery = v.Encode()
264 » return http.NewRequest("POST", u.String(), nil)
265 } 285 }
266 286
267 // Used for decoding the response body. 287 // Used for decoding the response body.
268 type respBody struct { 288 type respBody struct {
269 Access string `json:"access_token"` 289 Access string `json:"access_token"`
270 Type string `json:"token_type"` 290 Type string `json:"token_type"`
271 ExpiresIn time.Duration `json:"expires_in"` 291 ExpiresIn time.Duration `json:"expires_in"`
272 } 292 }
273 293
274 // HandleResponse returns an *oauth.Token given the *http.Response from a 294 // handleResponse returns a filled in *oauth.Token given the *http.Response from
275 // *http.Request created by BuildRequest. 295 // a *http.Request created by buildRequest.
276 func handleResponse(r *http.Response) (*oauth.Token, error) { 296 func handleResponse(r *http.Response) (*oauth.Token, error) {
277 o := &oauth.Token{} 297 o := &oauth.Token{}
278 defer r.Body.Close() 298 defer r.Body.Close()
279 if r.StatusCode != 200 { 299 if r.StatusCode != 200 {
280 return o, errors.New("invalid response: " + r.Status) 300 return o, errors.New("invalid response: " + r.Status)
281 } 301 }
282 b := &respBody{} 302 b := &respBody{}
283 err := json.NewDecoder(r.Body).Decode(b) 303 err := json.NewDecoder(r.Body).Decode(b)
284 if err != nil { 304 if err != nil {
285 return o, err 305 return o, err
286 } 306 }
287 o.AccessToken = b.Access 307 o.AccessToken = b.Access
288 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second) 308 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
289 return o, nil 309 return o, nil
290 } 310 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b