oauth2/google/internal/externalaccount/impersonate.go

81 lines
2.4 KiB
Go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package externalaccount
import (
"bytes"
"encoding/json"
"fmt"
"golang.org/x/oauth2"
"io"
"io/ioutil"
"net/http"
"time"
)
// generateAccesstokenReq is used for service account impersonation
type generateAccessTokenReq struct {
Delegates []string `json:"delegates,omitempty"`
Lifetime string `json:"lifetime,omitempty"`
Scope []string `json:"scope,omitempty"`
}
type impersonateTokenResponse struct {
AccessToken string `json:"accessToken"`
ExpireTime string `json:"expireTime"`
}
// impersonate performs the exchange to get a temporary service account
func (ts tokenSource) impersonate() (*oauth2.Token, error) {
reqBody := generateAccessTokenReq{
Lifetime: "3600s",
Scope: ts.conf.Scopes,
}
b, err := json.Marshal(reqBody)
serviceAccountImpersonationURL := ts.conf.ServiceAccountImpersonationURL
ts.conf.ServiceAccountImpersonationURL = ""
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
client := oauth2.NewClient(ts.ctx, ts)
if err != nil {
return &oauth2.Token{}, fmt.Errorf("google: unable to marshal request: %v", err)
}
req, err := http.NewRequest("POST", serviceAccountImpersonationURL, bytes.NewReader(b))
if err != nil {
return nil, fmt.Errorf("impersonate: unable to create request: %v", err)
}
req = req.WithContext(ts.ctx)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("impersonate: unable to generate access token: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, fmt.Errorf("impersonate: unable to read body: %v", err)
}
if c := resp.StatusCode; c < 200 || c > 299 {
return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
}
var accessTokenResp impersonateTokenResponse
if err := json.Unmarshal(body, &accessTokenResp); err != nil {
return nil, fmt.Errorf("impersonate: unable to parse response: %v", err)
}
expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
if err != nil {
return nil, fmt.Errorf("impersonate: unable to parse expiry: %v", err)
}
return &oauth2.Token{
AccessToken: accessTokenResp.AccessToken,
Expiry: expiry,
TokenType: "Bearer",
}, nil
}