1
2
3
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
42
43 type SDKConfig struct {
44 conf oauth2.Config
45 initialToken *oauth2.Token
46 }
47
48
49
50
51
52
53
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
124
125
126
127
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
137
138
139
140
141 func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
142 return c.conf.TokenSource(ctx, c.initialToken)
143 }
144
145
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 "": {},
153 }
154 scanner := bufio.NewScanner(ini)
155 currentSection := ""
156 for scanner.Scan() {
157 line := strings.TrimSpace(scanner.Text())
158 if strings.HasPrefix(line, ";") {
159
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
179
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
193 if v := os.Getenv("HOME"); v != "" {
194 return v
195 }
196
197 if u, err := user.Current(); err == nil {
198 return u.HomeDir
199 }
200 return ""
201 }
202
View as plain text