]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/golang.org/x/oauth2/jwt/jwt.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / golang.org / x / oauth2 / jwt / jwt.go
1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
6 // known as "two-legged OAuth 2.0".
7 //
8 // See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
9 package jwt
10
11 import (
12 "context"
13 "encoding/json"
14 "fmt"
15 "io"
16 "io/ioutil"
17 "net/http"
18 "net/url"
19 "strings"
20 "time"
21
22 "golang.org/x/oauth2"
23 "golang.org/x/oauth2/internal"
24 "golang.org/x/oauth2/jws"
25 )
26
27 var (
28 defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
29 defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
30 )
31
32 // Config is the configuration for using JWT to fetch tokens,
33 // commonly known as "two-legged OAuth 2.0".
34 type Config struct {
35 // Email is the OAuth client identifier used when communicating with
36 // the configured OAuth provider.
37 Email string
38
39 // PrivateKey contains the contents of an RSA private key or the
40 // contents of a PEM file that contains a private key. The provided
41 // private key is used to sign JWT payloads.
42 // PEM containers with a passphrase are not supported.
43 // Use the following command to convert a PKCS 12 file into a PEM.
44 //
45 // $ openssl pkcs12 -in key.p12 -out key.pem -nodes
46 //
47 PrivateKey []byte
48
49 // PrivateKeyID contains an optional hint indicating which key is being
50 // used.
51 PrivateKeyID string
52
53 // Subject is the optional user to impersonate.
54 Subject string
55
56 // Scopes optionally specifies a list of requested permission scopes.
57 Scopes []string
58
59 // TokenURL is the endpoint required to complete the 2-legged JWT flow.
60 TokenURL string
61
62 // Expires optionally specifies how long the token is valid for.
63 Expires time.Duration
64
65 // Audience optionally specifies the intended audience of the
66 // request. If empty, the value of TokenURL is used as the
67 // intended audience.
68 Audience string
69 }
70
71 // TokenSource returns a JWT TokenSource using the configuration
72 // in c and the HTTP client from the provided context.
73 func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
74 return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
75 }
76
77 // Client returns an HTTP client wrapping the context's
78 // HTTP transport and adding Authorization headers with tokens
79 // obtained from c.
80 //
81 // The returned client and its Transport should not be modified.
82 func (c *Config) Client(ctx context.Context) *http.Client {
83 return oauth2.NewClient(ctx, c.TokenSource(ctx))
84 }
85
86 // jwtSource is a source that always does a signed JWT request for a token.
87 // It should typically be wrapped with a reuseTokenSource.
88 type jwtSource struct {
89 ctx context.Context
90 conf *Config
91 }
92
93 func (js jwtSource) Token() (*oauth2.Token, error) {
94 pk, err := internal.ParseKey(js.conf.PrivateKey)
95 if err != nil {
96 return nil, err
97 }
98 hc := oauth2.NewClient(js.ctx, nil)
99 claimSet := &jws.ClaimSet{
100 Iss: js.conf.Email,
101 Scope: strings.Join(js.conf.Scopes, " "),
102 Aud: js.conf.TokenURL,
103 }
104 if subject := js.conf.Subject; subject != "" {
105 claimSet.Sub = subject
106 // prn is the old name of sub. Keep setting it
107 // to be compatible with legacy OAuth 2.0 providers.
108 claimSet.Prn = subject
109 }
110 if t := js.conf.Expires; t > 0 {
111 claimSet.Exp = time.Now().Add(t).Unix()
112 }
113 if aud := js.conf.Audience; aud != "" {
114 claimSet.Aud = aud
115 }
116 h := *defaultHeader
117 h.KeyID = js.conf.PrivateKeyID
118 payload, err := jws.Encode(&h, claimSet, pk)
119 if err != nil {
120 return nil, err
121 }
122 v := url.Values{}
123 v.Set("grant_type", defaultGrantType)
124 v.Set("assertion", payload)
125 resp, err := hc.PostForm(js.conf.TokenURL, v)
126 if err != nil {
127 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
128 }
129 defer resp.Body.Close()
130 body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
131 if err != nil {
132 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
133 }
134 if c := resp.StatusCode; c < 200 || c > 299 {
135 return nil, &oauth2.RetrieveError{
136 Response: resp,
137 Body: body,
138 }
139 }
140 // tokenRes is the JSON response body.
141 var tokenRes struct {
142 AccessToken string `json:"access_token"`
143 TokenType string `json:"token_type"`
144 IDToken string `json:"id_token"`
145 ExpiresIn int64 `json:"expires_in"` // relative seconds from now
146 }
147 if err := json.Unmarshal(body, &tokenRes); err != nil {
148 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
149 }
150 token := &oauth2.Token{
151 AccessToken: tokenRes.AccessToken,
152 TokenType: tokenRes.TokenType,
153 }
154 raw := make(map[string]interface{})
155 json.Unmarshal(body, &raw) // no error checks for optional fields
156 token = token.WithExtra(raw)
157
158 if secs := tokenRes.ExpiresIn; secs > 0 {
159 token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
160 }
161 if v := tokenRes.IDToken; v != "" {
162 // decode returned id token to get expiry
163 claimSet, err := jws.Decode(v)
164 if err != nil {
165 return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
166 }
167 token.Expiry = time.Unix(claimSet.Exp, 0)
168 }
169 return token, nil
170 }