golang.org/x/oauth2@v0.18.0/google/internal/stsexchange/sts_exchange.go (about) 1 // Copyright 2020 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 stsexchange 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "net/url" 15 "strconv" 16 "strings" 17 18 "golang.org/x/oauth2" 19 ) 20 21 func defaultHeader() http.Header { 22 header := make(http.Header) 23 header.Add("Content-Type", "application/x-www-form-urlencoded") 24 return header 25 } 26 27 // ExchangeToken performs an oauth2 token exchange with the provided endpoint. 28 // The first 4 fields are all mandatory. headers can be used to pass additional 29 // headers beyond the bare minimum required by the token exchange. options can 30 // be used to pass additional JSON-structured options to the remote server. 31 func ExchangeToken(ctx context.Context, endpoint string, request *TokenExchangeRequest, authentication ClientAuthentication, headers http.Header, options map[string]interface{}) (*Response, error) { 32 data := url.Values{} 33 data.Set("audience", request.Audience) 34 data.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") 35 data.Set("requested_token_type", "urn:ietf:params:oauth:token-type:access_token") 36 data.Set("subject_token_type", request.SubjectTokenType) 37 data.Set("subject_token", request.SubjectToken) 38 data.Set("scope", strings.Join(request.Scope, " ")) 39 if options != nil { 40 opts, err := json.Marshal(options) 41 if err != nil { 42 return nil, fmt.Errorf("oauth2/google: failed to marshal additional options: %v", err) 43 } 44 data.Set("options", string(opts)) 45 } 46 47 return makeRequest(ctx, endpoint, data, authentication, headers) 48 } 49 50 func RefreshAccessToken(ctx context.Context, endpoint string, refreshToken string, authentication ClientAuthentication, headers http.Header) (*Response, error) { 51 data := url.Values{} 52 data.Set("grant_type", "refresh_token") 53 data.Set("refresh_token", refreshToken) 54 55 return makeRequest(ctx, endpoint, data, authentication, headers) 56 } 57 58 func makeRequest(ctx context.Context, endpoint string, data url.Values, authentication ClientAuthentication, headers http.Header) (*Response, error) { 59 if headers == nil { 60 headers = defaultHeader() 61 } 62 client := oauth2.NewClient(ctx, nil) 63 authentication.InjectAuthentication(data, headers) 64 encodedData := data.Encode() 65 66 req, err := http.NewRequest("POST", endpoint, strings.NewReader(encodedData)) 67 if err != nil { 68 return nil, fmt.Errorf("oauth2/google: failed to properly build http request: %v", err) 69 } 70 req = req.WithContext(ctx) 71 for key, list := range headers { 72 for _, val := range list { 73 req.Header.Add(key, val) 74 } 75 } 76 req.Header.Add("Content-Length", strconv.Itoa(len(encodedData))) 77 78 resp, err := client.Do(req) 79 80 if err != nil { 81 return nil, fmt.Errorf("oauth2/google: invalid response from Secure Token Server: %v", err) 82 } 83 defer resp.Body.Close() 84 85 body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) 86 if err != nil { 87 return nil, err 88 } 89 if c := resp.StatusCode; c < 200 || c > 299 { 90 return nil, fmt.Errorf("oauth2/google: status code %d: %s", c, body) 91 } 92 var stsResp Response 93 err = json.Unmarshal(body, &stsResp) 94 if err != nil { 95 return nil, fmt.Errorf("oauth2/google: failed to unmarshal response body from Secure Token Server: %v", err) 96 97 } 98 99 return &stsResp, nil 100 } 101 102 // TokenExchangeRequest contains fields necessary to make an oauth2 token exchange. 103 type TokenExchangeRequest struct { 104 ActingParty struct { 105 ActorToken string 106 ActorTokenType string 107 } 108 GrantType string 109 Resource string 110 Audience string 111 Scope []string 112 RequestedTokenType string 113 SubjectToken string 114 SubjectTokenType string 115 } 116 117 // Response is used to decode the remote server response during an oauth2 token exchange. 118 type Response struct { 119 AccessToken string `json:"access_token"` 120 IssuedTokenType string `json:"issued_token_type"` 121 TokenType string `json:"token_type"` 122 ExpiresIn int `json:"expires_in"` 123 Scope string `json:"scope"` 124 RefreshToken string `json:"refresh_token"` 125 }