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