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.
Passed
Push — master ( 3e3736...b588b4 )
by
unknown
09:18 queued 19s
created

ider.getOAuthTokenUpdateCallback   A

Complexity

Conditions 2

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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