Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / golang.org / x / oauth2 / google / sdk.go
1 // Copyright 2015 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 google
6
7 import (
8         "bufio"
9         "context"
10         "encoding/json"
11         "errors"
12         "fmt"
13         "io"
14         "net/http"
15         "os"
16         "os/user"
17         "path/filepath"
18         "runtime"
19         "strings"
20         "time"
21
22         "golang.org/x/oauth2"
23 )
24
25 type sdkCredentials struct {
26         Data []struct {
27                 Credential struct {
28                         ClientID     string     `json:"client_id"`
29                         ClientSecret string     `json:"client_secret"`
30                         AccessToken  string     `json:"access_token"`
31                         RefreshToken string     `json:"refresh_token"`
32                         TokenExpiry  *time.Time `json:"token_expiry"`
33                 } `json:"credential"`
34                 Key struct {
35                         Account string `json:"account"`
36                         Scope   string `json:"scope"`
37                 } `json:"key"`
38         }
39 }
40
41 // An SDKConfig provides access to tokens from an account already
42 // authorized via the Google Cloud SDK.
43 type SDKConfig struct {
44         conf         oauth2.Config
45         initialToken *oauth2.Token
46 }
47
48 // NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
49 // account. If account is empty, the account currently active in
50 // Google Cloud SDK properties is used.
51 // Google Cloud SDK credentials must be created by running `gcloud auth`
52 // before using this function.
53 // The Google Cloud SDK is available at https://cloud.google.com/sdk/.
54 func NewSDKConfig(account string) (*SDKConfig, error) {
55         configPath, err := sdkConfigPath()
56         if err != nil {
57                 return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err)
58         }
59         credentialsPath := filepath.Join(configPath, "credentials")
60         f, err := os.Open(credentialsPath)
61         if err != nil {
62                 return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err)
63         }
64         defer f.Close()
65
66         var c sdkCredentials
67         if err := json.NewDecoder(f).Decode(&c); err != nil {
68                 return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err)
69         }
70         if len(c.Data) == 0 {
71                 return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath)
72         }
73         if account == "" {
74                 propertiesPath := filepath.Join(configPath, "properties")
75                 f, err := os.Open(propertiesPath)
76                 if err != nil {
77                         return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
78                 }
79                 defer f.Close()
80                 ini, err := parseINI(f)
81                 if err != nil {
82                         return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
83                 }
84                 core, ok := ini["core"]
85                 if !ok {
86                         return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini)
87                 }
88                 active, ok := core["account"]
89                 if !ok {
90                         return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core)
91                 }
92                 account = active
93         }
94
95         for _, d := range c.Data {
96                 if account == "" || d.Key.Account == account {
97                         if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" {
98                                 return nil, fmt.Errorf("oauth2/google: no token available for account %q", account)
99                         }
100                         var expiry time.Time
101                         if d.Credential.TokenExpiry != nil {
102                                 expiry = *d.Credential.TokenExpiry
103                         }
104                         return &SDKConfig{
105                                 conf: oauth2.Config{
106                                         ClientID:     d.Credential.ClientID,
107                                         ClientSecret: d.Credential.ClientSecret,
108                                         Scopes:       strings.Split(d.Key.Scope, " "),
109                                         Endpoint:     Endpoint,
110                                         RedirectURL:  "oob",
111                                 },
112                                 initialToken: &oauth2.Token{
113                                         AccessToken:  d.Credential.AccessToken,
114                                         RefreshToken: d.Credential.RefreshToken,
115                                         Expiry:       expiry,
116                                 },
117                         }, nil
118                 }
119         }
120         return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account)
121 }
122
123 // Client returns an HTTP client using Google Cloud SDK credentials to
124 // authorize requests. The token will auto-refresh as necessary. The
125 // underlying http.RoundTripper will be obtained using the provided
126 // context. The returned client and its Transport should not be
127 // modified.
128 func (c *SDKConfig) Client(ctx context.Context) *http.Client {
129         return &http.Client{
130                 Transport: &oauth2.Transport{
131                         Source: c.TokenSource(ctx),
132                 },
133         }
134 }
135
136 // TokenSource returns an oauth2.TokenSource that retrieve tokens from
137 // Google Cloud SDK credentials using the provided context.
138 // It will returns the current access token stored in the credentials,
139 // and refresh it when it expires, but it won't update the credentials
140 // with the new access token.
141 func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
142         return c.conf.TokenSource(ctx, c.initialToken)
143 }
144
145 // Scopes are the OAuth 2.0 scopes the current account is authorized for.
146 func (c *SDKConfig) Scopes() []string {
147         return c.conf.Scopes
148 }
149
150 func parseINI(ini io.Reader) (map[string]map[string]string, error) {
151         result := map[string]map[string]string{
152                 "": {}, // root section
153         }
154         scanner := bufio.NewScanner(ini)
155         currentSection := ""
156         for scanner.Scan() {
157                 line := strings.TrimSpace(scanner.Text())
158                 if strings.HasPrefix(line, ";") {
159                         // comment.
160                         continue
161                 }
162                 if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
163                         currentSection = strings.TrimSpace(line[1 : len(line)-1])
164                         result[currentSection] = map[string]string{}
165                         continue
166                 }
167                 parts := strings.SplitN(line, "=", 2)
168                 if len(parts) == 2 && parts[0] != "" {
169                         result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
170                 }
171         }
172         if err := scanner.Err(); err != nil {
173                 return nil, fmt.Errorf("error scanning ini: %v", err)
174         }
175         return result, nil
176 }
177
178 // sdkConfigPath tries to guess where the gcloud config is located.
179 // It can be overridden during tests.
180 var sdkConfigPath = func() (string, error) {
181         if runtime.GOOS == "windows" {
182                 return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil
183         }
184         homeDir := guessUnixHomeDir()
185         if homeDir == "" {
186                 return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
187         }
188         return filepath.Join(homeDir, ".config", "gcloud"), nil
189 }
190
191 func guessUnixHomeDir() string {
192         // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470
193         if v := os.Getenv("HOME"); v != "" {
194                 return v
195         }
196         // Else, fall back to user.Current:
197         if u, err := user.Current(); err == nil {
198                 return u.HomeDir
199         }
200         return ""
201 }