1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package stsexchange
16
17 import (
18 "context"
19 "encoding/base64"
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "net/url"
24 "strconv"
25 "strings"
26
27 "cloud.google.com/go/auth"
28 "cloud.google.com/go/auth/internal"
29 )
30
31 const (
32
33 GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
34
35 TokenType = "urn:ietf:params:oauth:token-type:access_token"
36
37 jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
38 )
39
40
41 type Options struct {
42 Client *http.Client
43 Endpoint string
44 Request *TokenRequest
45 Authentication ClientAuthentication
46 Headers http.Header
47
48
49 ExtraOpts map[string]interface{}
50 RefreshToken string
51 }
52
53
54 func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
55 data := url.Values{}
56 data.Set("grant_type", "refresh_token")
57 data.Set("refresh_token", opts.RefreshToken)
58 return doRequest(ctx, opts, data)
59 }
60
61
62 func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
63 data := url.Values{}
64 data.Set("audience", opts.Request.Audience)
65 data.Set("grant_type", GrantType)
66 data.Set("requested_token_type", TokenType)
67 data.Set("subject_token_type", opts.Request.SubjectTokenType)
68 data.Set("subject_token", opts.Request.SubjectToken)
69 data.Set("scope", strings.Join(opts.Request.Scope, " "))
70 if opts.ExtraOpts != nil {
71 opts, err := json.Marshal(opts.ExtraOpts)
72 if err != nil {
73 return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
74 }
75 data.Set("options", string(opts))
76 }
77 return doRequest(ctx, opts, data)
78 }
79
80 func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
81 opts.Authentication.InjectAuthentication(data, opts.Headers)
82 encodedData := data.Encode()
83
84 req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
85 if err != nil {
86 return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
87
88 }
89 for key, list := range opts.Headers {
90 for _, val := range list {
91 req.Header.Add(key, val)
92 }
93 }
94 req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
95
96 resp, err := opts.Client.Do(req)
97 if err != nil {
98 return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
99 }
100 defer resp.Body.Close()
101
102 body, err := internal.ReadAll(resp.Body)
103 if err != nil {
104 return nil, err
105 }
106 if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
107 return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
108 }
109 var stsResp TokenResponse
110 if err := json.Unmarshal(body, &stsResp); err != nil {
111 return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
112 }
113
114 return &stsResp, nil
115 }
116
117
118
119 type TokenRequest struct {
120 ActingParty struct {
121 ActorToken string
122 ActorTokenType string
123 }
124 GrantType string
125 Resource string
126 Audience string
127 Scope []string
128 RequestedTokenType string
129 SubjectToken string
130 SubjectTokenType string
131 }
132
133
134
135 type TokenResponse struct {
136 AccessToken string `json:"access_token"`
137 IssuedTokenType string `json:"issued_token_type"`
138 TokenType string `json:"token_type"`
139 ExpiresIn int `json:"expires_in"`
140 Scope string `json:"scope"`
141 RefreshToken string `json:"refresh_token"`
142 }
143
144
145
146 type ClientAuthentication struct {
147 AuthStyle auth.Style
148 ClientID string
149 ClientSecret string
150 }
151
152
153
154
155 func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
156 if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
157 return
158 }
159 switch c.AuthStyle {
160 case auth.StyleInHeader:
161 plainHeader := c.ClientID + ":" + c.ClientSecret
162 headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
163 default:
164 values.Set("client_id", c.ClientID)
165 values.Set("client_secret", c.ClientSecret)
166 }
167 }
168
View as plain text