GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

credentials/providers/cli_profile.go   F
last analyzed

Size/Duplication

Total Lines 435
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 60
eloc 290
dl 0
loc 435
rs 3.6
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A providers.*CLIProfileCredentialsProvider.GetProviderName 0 2 1
B providers.*CLIProfileCredentialsProvider.writeConfigurationToFileWithLock 0 41 6
A providers.NewCLIProfileCredentialsProviderBuilder 0 3 1
F providers.*CLIProfileCredentialsProvider.getCredentialsProvider 0 96 14
A providers.newConfigurationFromPath 0 21 5
A providers.*CLIProfileCredentialsProviderBuilder.WithProfileFile 0 3 1
B providers.*CLIProfileCredentialsProvider.writeConfigFile 0 21 6
A providers.*CLIProfileCredentialsProviderBuilder.Build 0 23 4
A providers.*CLIProfileCredentialsProviderBuilder.WithProfileName 0 3 1
C providers.*CLIProfileCredentialsProvider.GetCredentials 0 48 9
A providers.*CLIProfileCredentialsProvider.getOAuthTokenUpdateCallback 0 3 2
A providers.*configuration.getProfile 0 10 3
A providers.*CLIProfileCredentialsProvider.updateOAuthTokens 0 27 3
A providers.*CLIProfileCredentialsProvider.writeConfigurationToFile 0 25 4
1
package providers
2
3
import (
4
	"encoding/json"
5
	"errors"
6
	"fmt"
7
	"io/ioutil"
8
	"os"
9
	"path"
10
	"strconv"
11
	"strings"
12
	"sync"
13
	"time"
14
15
	"github.com/aliyun/credentials-go/credentials/internal/utils"
16
)
17
18
type CLIProfileCredentialsProvider struct {
19
	profileFile   string
20
	profileName   string
21
	innerProvider CredentialsProvider
22
	// 文件锁,用于并发安全
23
	fileMutex sync.RWMutex
24
}
25
26
type CLIProfileCredentialsProviderBuilder struct {
27
	provider *CLIProfileCredentialsProvider
28
}
29
30
func (b *CLIProfileCredentialsProviderBuilder) WithProfileFile(profileFile string) *CLIProfileCredentialsProviderBuilder {
31
	b.provider.profileFile = profileFile
32
	return b
33
}
34
35
func (b *CLIProfileCredentialsProviderBuilder) WithProfileName(profileName string) *CLIProfileCredentialsProviderBuilder {
36
	b.provider.profileName = profileName
37
	return b
38
}
39
40
func (b *CLIProfileCredentialsProviderBuilder) Build() (provider *CLIProfileCredentialsProvider, err error) {
41
	// 优先级:
42
	// 1. 使用显示指定的 profileFile
43
	// 2. 使用环境变量(ALIBABA_CLOUD_CONFIG_FILE)指定的 profileFile
44
	// 3. 兜底使用 path.Join(homeDir, ".aliyun/config") 作为 profileFile
45
	if b.provider.profileFile == "" {
46
		b.provider.profileFile = os.Getenv("ALIBABA_CLOUD_CONFIG_FILE")
47
	}
48
	// 优先级:
49
	// 1. 使用显示指定的 profileName
50
	// 2. 使用环境变量(ALIBABA_CLOUD_PROFILE)制定的 profileName
51
	// 3. 使用 CLI 配置中的当前 profileName
52
	if b.provider.profileName == "" {
53
		b.provider.profileName = os.Getenv("ALIBABA_CLOUD_PROFILE")
54
	}
55
56
	if strings.ToLower(os.Getenv("ALIBABA_CLOUD_CLI_PROFILE_DISABLED")) == "true" {
57
		err = errors.New("the CLI profile is disabled")
58
		return
59
	}
60
61
	provider = b.provider
62
	return
63
}
64
65
func NewCLIProfileCredentialsProviderBuilder() *CLIProfileCredentialsProviderBuilder {
66
	return &CLIProfileCredentialsProviderBuilder{
67
		provider: &CLIProfileCredentialsProvider{},
68
	}
69
}
70
71
type profile struct {
72
	Name                   string `json:"name"`
73
	Mode                   string `json:"mode"`
74
	AccessKeyID            string `json:"access_key_id"`
75
	AccessKeySecret        string `json:"access_key_secret"`
76
	SecurityToken          string `json:"sts_token"`
77
	RegionID               string `json:"region_id"`
78
	RoleArn                string `json:"ram_role_arn"`
79
	RoleSessionName        string `json:"ram_session_name"`
80
	DurationSeconds        int    `json:"expired_seconds"`
81
	StsRegion              string `json:"sts_region"`
82
	EnableVpc              bool   `json:"enable_vpc"`
83
	SourceProfile          string `json:"source_profile"`
84
	RoleName               string `json:"ram_role_name"`
85
	OIDCTokenFile          string `json:"oidc_token_file"`
86
	OIDCProviderARN        string `json:"oidc_provider_arn"`
87
	Policy                 string `json:"policy"`
88
	ExternalId             string `json:"external_id"`
89
	SignInUrl              string `json:"cloud_sso_sign_in_url"`
90
	AccountId              string `json:"cloud_sso_account_id"`
91
	AccessConfig           string `json:"cloud_sso_access_config"`
92
	AccessToken            string `json:"access_token"`
93
	AccessTokenExpire      int64  `json:"cloud_sso_access_token_expire"`
94
	OauthSiteType          string `json:"oauth_site_type"`
95
	OauthRefreshToken      string `json:"oauth_refresh_token"`
96
	OauthAccessToken       string `json:"oauth_access_token"`
97
	OauthAccessTokenExpire int64  `json:"oauth_access_token_expire"`
98
	StsExpire              int64  `json:"sts_expiration"`
99
}
100
101
type configuration struct {
102
	Current  string     `json:"current"`
103
	Profiles []*profile `json:"profiles"`
104
}
105
106
func newConfigurationFromPath(cfgPath string) (conf *configuration, err error) {
107
	bytes, err := ioutil.ReadFile(cfgPath)
108
	if err != nil {
109
		err = fmt.Errorf("reading aliyun cli config from '%s' failed %v", cfgPath, err)
110
		return
111
	}
112
113
	conf = &configuration{}
114
115
	err = json.Unmarshal(bytes, conf)
116
	if err != nil {
117
		err = fmt.Errorf("unmarshal aliyun cli config from '%s' failed: %s", cfgPath, string(bytes))
118
		return
119
	}
120
121
	if conf.Profiles == nil || len(conf.Profiles) == 0 {
122
		err = fmt.Errorf("no any configured profiles in '%s'", cfgPath)
123
		return
124
	}
125
126
	return
127
}
128
129
func (conf *configuration) getProfile(name string) (profile *profile, err error) {
130
	for _, p := range conf.Profiles {
131
		if p.Name == name {
132
			profile = p
133
			return
134
		}
135
	}
136
137
	err = fmt.Errorf("unable to get profile with '%s'", name)
138
	return
139
}
140
141
var oauthBaseUrlMap = map[string]string{
142
	"CN":   "https://oauth.aliyun.com",
143
	"INTL": "https://oauth.alibabacloud.com",
144
}
145
146
var oauthClientMap = map[string]string{
147
	"CN":   "4038181954557748008",
148
	"INTL": "4103531455503354461",
149
}
150
151
func (provider *CLIProfileCredentialsProvider) getCredentialsProvider(conf *configuration, profileName string) (credentialsProvider CredentialsProvider, err error) {
152
	p, err := conf.getProfile(profileName)
153
	if err != nil {
154
		return
155
	}
156
157
	switch p.Mode {
158
	case "AK":
159
		credentialsProvider, err = NewStaticAKCredentialsProviderBuilder().
160
			WithAccessKeyId(p.AccessKeyID).
161
			WithAccessKeySecret(p.AccessKeySecret).
162
			Build()
163
	case "StsToken":
164
		credentialsProvider, err = NewStaticSTSCredentialsProviderBuilder().
165
			WithAccessKeyId(p.AccessKeyID).
166
			WithAccessKeySecret(p.AccessKeySecret).
167
			WithSecurityToken(p.SecurityToken).
168
			Build()
169
	case "RamRoleArn":
170
		previousProvider, err1 := NewStaticAKCredentialsProviderBuilder().
171
			WithAccessKeyId(p.AccessKeyID).
172
			WithAccessKeySecret(p.AccessKeySecret).
173
			Build()
174
		if err1 != nil {
175
			return nil, err1
176
		}
177
178
		credentialsProvider, err = NewRAMRoleARNCredentialsProviderBuilder().
179
			WithCredentialsProvider(previousProvider).
180
			WithRoleArn(p.RoleArn).
181
			WithRoleSessionName(p.RoleSessionName).
182
			WithDurationSeconds(p.DurationSeconds).
183
			WithStsRegionId(p.StsRegion).
184
			WithEnableVpc(p.EnableVpc).
185
			WithPolicy(p.Policy).
186
			WithExternalId(p.ExternalId).
187
			Build()
188
	case "EcsRamRole":
189
		credentialsProvider, err = NewECSRAMRoleCredentialsProviderBuilder().WithRoleName(p.RoleName).Build()
190
	case "OIDC":
191
		credentialsProvider, err = NewOIDCCredentialsProviderBuilder().
192
			WithOIDCTokenFilePath(p.OIDCTokenFile).
193
			WithOIDCProviderARN(p.OIDCProviderARN).
194
			WithRoleArn(p.RoleArn).
195
			WithStsRegionId(p.StsRegion).
196
			WithEnableVpc(p.EnableVpc).
197
			WithDurationSeconds(p.DurationSeconds).
198
			WithRoleSessionName(p.RoleSessionName).
199
			WithPolicy(p.Policy).
200
			Build()
201
	case "ChainableRamRoleArn":
202
		previousProvider, err1 := provider.getCredentialsProvider(conf, p.SourceProfile)
203
		if err1 != nil {
204
			err = fmt.Errorf("get source profile failed: %s", err1.Error())
205
			return
206
		}
207
		credentialsProvider, err = NewRAMRoleARNCredentialsProviderBuilder().
208
			WithCredentialsProvider(previousProvider).
209
			WithRoleArn(p.RoleArn).
210
			WithRoleSessionName(p.RoleSessionName).
211
			WithDurationSeconds(p.DurationSeconds).
212
			WithStsRegionId(p.StsRegion).
213
			WithEnableVpc(p.EnableVpc).
214
			WithPolicy(p.Policy).
215
			WithExternalId(p.ExternalId).
216
			Build()
217
	case "CloudSSO":
218
		credentialsProvider, err = NewCloudSSOCredentialsProviderBuilder().
219
			WithSignInUrl(p.SignInUrl).
220
			WithAccountId(p.AccountId).
221
			WithAccessConfig(p.AccessConfig).
222
			WithAccessToken(p.AccessToken).
223
			WithAccessTokenExpire(p.AccessTokenExpire).
224
			Build()
225
	case "OAuth":
226
		siteType := strings.ToUpper(p.OauthSiteType)
227
		signInUrl := oauthBaseUrlMap[siteType]
228
		if signInUrl == "" {
229
			err = fmt.Errorf("invalid site type, support CN or INTL")
230
			return
231
		}
232
		clientId := oauthClientMap[siteType]
233
234
		credentialsProvider, err = NewOAuthCredentialsProviderBuilder().
235
			WithSignInUrl(signInUrl).
236
			WithClientId(clientId).
237
			WithRefreshToken(p.OauthRefreshToken).
238
			WithAccessToken(p.OauthAccessToken).
239
			WithAccessTokenExpire(p.OauthAccessTokenExpire).
240
			WithTokenUpdateCallback(provider.getOAuthTokenUpdateCallback()).
241
			Build()
242
	default:
243
		err = fmt.Errorf("unsupported profile mode '%s'", p.Mode)
244
	}
245
246
	return
247
}
248
249
// 默认设置为 GetHomePath,测试时便于 mock
250
var getHomePath = utils.GetHomePath
251
252
func (provider *CLIProfileCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
253
	if provider.innerProvider == nil {
254
		cfgPath := provider.profileFile
255
		if cfgPath == "" {
256
			homeDir := getHomePath()
257
			if homeDir == "" {
258
				err = fmt.Errorf("cannot found home dir")
259
				return
260
			}
261
262
			cfgPath = path.Join(homeDir, ".aliyun/config.json")
263
			provider.profileFile = cfgPath
264
		}
265
266
		conf, err1 := newConfigurationFromPath(cfgPath)
267
		if err1 != nil {
268
			err = err1
269
			return
270
		}
271
272
		if provider.profileName == "" {
273
			provider.profileName = conf.Current
274
		}
275
276
		provider.innerProvider, err = provider.getCredentialsProvider(conf, provider.profileName)
277
		if err != nil {
278
			return
279
		}
280
	}
281
282
	innerCC, err := provider.innerProvider.GetCredentials()
283
	if err != nil {
284
		return
285
	}
286
287
	providerName := innerCC.ProviderName
288
	if providerName == "" {
289
		providerName = provider.innerProvider.GetProviderName()
290
	}
291
292
	cc = &Credentials{
293
		AccessKeyId:     innerCC.AccessKeyId,
294
		AccessKeySecret: innerCC.AccessKeySecret,
295
		SecurityToken:   innerCC.SecurityToken,
296
		ProviderName:    fmt.Sprintf("%s/%s", provider.GetProviderName(), providerName),
297
	}
298
299
	return
300
}
301
302
func (provider *CLIProfileCredentialsProvider) GetProviderName() string {
303
	return "cli_profile"
304
}
305
306
// updateOAuthTokens 更新OAuth令牌并写回配置文件
307
func (provider *CLIProfileCredentialsProvider) updateOAuthTokens(refreshToken, accessToken, accessKey, secret, securityToken string, accessTokenExpire, stsExpire int64) error {
308
	provider.fileMutex.Lock()
309
	defer provider.fileMutex.Unlock()
310
311
	cfgPath := provider.profileFile
312
	conf, err := newConfigurationFromPath(cfgPath)
313
	if err != nil {
314
		return fmt.Errorf("failed to read config file: %v", err)
315
	}
316
317
	profileName := provider.profileName
318
	profile, err := conf.getProfile(profileName)
319
	if err != nil {
320
		return fmt.Errorf("failed to get profile %s: %v", profileName, err)
321
	}
322
323
	// update
324
	profile.OauthRefreshToken = refreshToken
325
	profile.OauthAccessToken = accessToken
326
	profile.OauthAccessTokenExpire = accessTokenExpire
327
	profile.AccessKeyID = accessKey
328
	profile.AccessKeySecret = secret
329
	profile.SecurityToken = securityToken
330
	profile.StsExpire = stsExpire
331
332
	// write back with file lock
333
	return provider.writeConfigurationToFileWithLock(cfgPath, conf)
334
}
335
336
// writeConfigurationToFile 将配置写入文件,使用原子写入确保数据完整性
337
func (provider *CLIProfileCredentialsProvider) writeConfigurationToFile(cfgPath string, conf *configuration) error {
338
	// 获取原文件权限(如果存在)
339
	fileMode := os.FileMode(0644)
340
	if stat, err := os.Stat(cfgPath); err == nil {
341
		fileMode = stat.Mode()
342
	}
343
344
	// 创建唯一临时文件
345
	tempFile := cfgPath + ".tmp-" + strconv.FormatInt(time.Now().UnixNano(), 10)
346
347
	// 写入临时文件
348
	err := provider.writeConfigFile(tempFile, fileMode, conf)
349
	if err != nil {
350
		return fmt.Errorf("failed to write temp file: %v", err)
351
	}
352
353
	// 原子性重命名,确保文件完整性
354
	err = os.Rename(tempFile, cfgPath)
355
	if err != nil {
356
		// 清理临时文件
357
		os.Remove(tempFile)
358
		return fmt.Errorf("failed to rename temp file: %v", err)
359
	}
360
361
	return nil
362
}
363
364
// writeConfigFile 写入配置文件
365
func (provider *CLIProfileCredentialsProvider) writeConfigFile(filename string, fileMode os.FileMode, conf *configuration) error {
366
	f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, fileMode)
367
	if err != nil {
368
		return fmt.Errorf("failed to create config file: %w", err)
369
	}
370
371
	defer func() {
372
		closeErr := f.Close()
373
		if err == nil && closeErr != nil {
374
			err = fmt.Errorf("failed to close config file: %w", closeErr)
375
		}
376
	}()
377
378
	encoder := json.NewEncoder(f)
379
	encoder.SetIndent("", "    ")
380
381
	if err = encoder.Encode(conf); err != nil {
382
		return fmt.Errorf("failed to serialize config: %w", err)
383
	}
384
385
	return nil
386
}
387
388
// writeConfigurationToFileWithLock 使用操作系统级别的文件锁写入配置文件
389
func (provider *CLIProfileCredentialsProvider) writeConfigurationToFileWithLock(cfgPath string, conf *configuration) error {
390
	// 获取原文件权限(如果存在)
391
	fileMode := os.FileMode(0644)
392
	if stat, err := os.Stat(cfgPath); err == nil {
393
		fileMode = stat.Mode()
394
	}
395
396
	// 打开文件用于锁定
397
	file, err := os.OpenFile(cfgPath, os.O_RDWR|os.O_CREATE, fileMode)
398
	if err != nil {
399
		return fmt.Errorf("failed to open config file: %v", err)
400
	}
401
402
	// 获取独占锁(阻塞其他进程)
403
	err = lockFile(int(file.Fd()))
404
	if err != nil {
405
		file.Close()
406
		return fmt.Errorf("failed to acquire file lock: %v", err)
407
	}
408
409
	// 创建唯一临时文件
410
	tempFile := cfgPath + ".tmp-" + strconv.FormatInt(time.Now().UnixNano(), 10)
411
	err = provider.writeConfigFile(tempFile, fileMode, conf)
412
	if err != nil {
413
		unlockFile(int(file.Fd()))
414
		file.Close()
415
		return fmt.Errorf("failed to write temp file: %v", err)
416
	}
417
418
	// 关闭并解锁原文件,以便在Windows上可以重命名
419
	unlockFile(int(file.Fd()))
420
	file.Close()
421
422
	// 原子性重命名
423
	err = os.Rename(tempFile, cfgPath)
424
	if err != nil {
425
		os.Remove(tempFile)
426
		return fmt.Errorf("failed to rename temp file: %v", err)
427
	}
428
429
	return nil
430
}
431
432
// getOAuthTokenUpdateCallback 获取OAuth令牌更新回调函数
433
func (provider *CLIProfileCredentialsProvider) getOAuthTokenUpdateCallback() OAuthTokenUpdateCallback {
434
	return func(refreshToken, accessToken, accessKey, secret, securityToken string, accessTokenExpire, stsExpire int64) error {
435
		return provider.updateOAuthTokens(refreshToken, accessToken, accessKey, secret, securityToken, accessTokenExpire, stsExpire)
436
	}
437
}
438