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 ( c2a7cb...51a090 )
by
unknown
08:03
created

viderBuilder.WithCredentialUpdateCallback   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
package providers
2
3
import (
4
	"bytes"
5
	"encoding/json"
6
	"errors"
7
	"fmt"
8
	"os/exec"
9
	"strings"
10
	"sync"
11
	"time"
12
)
13
14
// ExternalCredentialUpdateCallback 定义External凭证更新回调函数类型
15
type ExternalCredentialUpdateCallback func(accessKeyId, accessKeySecret, securityToken string, expiration int64) error
16
17
type externalCredentialResponse struct {
18
	Mode            string `json:"mode"`
19
	AccessKeyId     string `json:"access_key_id"`
20
	AccessKeySecret string `json:"access_key_secret"`
21
	SecurityToken   string `json:"sts_token"`
22
	Expiration      string `json:"expiration,omitempty"`
23
}
24
25
type ExternalCredentialsProvider struct {
26
	processCommand string
27
28
	lastUpdateTimestamp int64
29
	expirationTimestamp int64
30
	sessionCredentials  *sessionCredentials
31
	// External credential call back
32
	credentialUpdateCallback ExternalCredentialUpdateCallback
33
	// 互斥锁,用于并发安全
34
	mu sync.RWMutex
35
}
36
37
type ExternalCredentialsProviderBuilder struct {
38
	provider *ExternalCredentialsProvider
39
}
40
41
func NewExternalCredentialsProviderBuilder() *ExternalCredentialsProviderBuilder {
42
	return &ExternalCredentialsProviderBuilder{
43
		provider: &ExternalCredentialsProvider{},
44
	}
45
}
46
47
func (b *ExternalCredentialsProviderBuilder) WithProcessCommand(processCommand string) *ExternalCredentialsProviderBuilder {
48
	b.provider.processCommand = processCommand
49
	return b
50
}
51
52
func (b *ExternalCredentialsProviderBuilder) WithCredentialUpdateCallback(callback ExternalCredentialUpdateCallback) *ExternalCredentialsProviderBuilder {
53
	b.provider.credentialUpdateCallback = callback
54
	return b
55
}
56
57
func (b *ExternalCredentialsProviderBuilder) Build() (provider *ExternalCredentialsProvider, err error) {
58
	if b.provider.processCommand == "" {
59
		err = errors.New("process_command is empty")
60
		return
61
	}
62
63
	provider = b.provider
64
	return
65
}
66
67
func (provider *ExternalCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
68
	args := strings.Fields(provider.processCommand)
69
	if len(args) == 0 {
70
		err = errors.New("process_command is empty")
71
		return
72
	}
73
74
	cmd := exec.Command(args[0], args[1:]...)
75
76
	// 创建一个buffer来捕获标准输出
77
	var stdoutBuf bytes.Buffer
78
	cmd.Stdout = &stdoutBuf
79
80
	// 创建一个buffer来捕获标准错误输出
81
	var stderrBuf bytes.Buffer
82
	cmd.Stderr = &stderrBuf
83
84
	// 执行命令
85
	err = cmd.Run()
86
	if err != nil {
87
		return nil, fmt.Errorf("failed to execute external command: %w\nstderr: %s", err, stderrBuf.String())
88
	}
89
90
	// 只解析标准输出
91
	buf := stdoutBuf.Bytes()
92
93
	// 解析得到凭证响应
94
	var resp externalCredentialResponse
95
	err = json.Unmarshal(buf, &resp)
96
	if err != nil {
97
		fmt.Println(provider.processCommand)
98
		fmt.Println(string(buf))
99
		return nil, fmt.Errorf("failed to parse external command output: %w", err)
100
	}
101
102
	// 验证返回的凭证数据
103
	if resp.AccessKeyId == "" || resp.AccessKeySecret == "" {
104
		return nil, fmt.Errorf("invalid credential response: access_key_id or access_key_secret is empty")
105
	}
106
107
	// 根据 mode 验证 SecurityToken
108
	if resp.Mode == "StsToken" && resp.SecurityToken == "" {
109
		return nil, fmt.Errorf("invalid StsToken credential response: sts_token is empty")
110
	}
111
112
	session = &sessionCredentials{
113
		AccessKeyId:     resp.AccessKeyId,
114
		AccessKeySecret: resp.AccessKeySecret,
115
		SecurityToken:   resp.SecurityToken,
116
		Expiration:      resp.Expiration,
117
	}
118
119
	return
120
}
121
122
func (provider *ExternalCredentialsProvider) needUpdateCredential() (result bool) {
123
	provider.mu.RLock()
124
	defer provider.mu.RUnlock()
125
126
	// 如果没有缓存凭证,需要更新
127
	if provider.sessionCredentials == nil {
128
		return true
129
	}
130
131
	// 如果没有过期时间,每次都更新(因为外部命令可能返回动态凭证)
132
	if provider.expirationTimestamp == 0 {
133
		return true
134
	}
135
136
	// 如果凭证即将过期(提前180秒),需要更新
137
	return provider.expirationTimestamp-time.Now().Unix() <= 180
138
}
139
140
func (provider *ExternalCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
141
	// 先检查是否需要更新(使用读锁)
142
	provider.mu.RLock()
143
	needUpdate := provider.sessionCredentials == nil ||
144
		provider.expirationTimestamp == 0 ||
145
		provider.expirationTimestamp-time.Now().Unix() <= 180
146
	provider.mu.RUnlock()
147
148
	if needUpdate {
149
		// 获取新凭证(在锁外执行,避免阻塞其他 goroutine)
150
		sessionCredentials, err1 := provider.getCredentials()
151
		if err1 != nil {
152
			return nil, err1
153
		}
154
155
		// 使用写锁更新共享状态
156
		provider.mu.Lock()
157
		// 双重检查,避免多个 goroutine 同时更新
158
		if provider.sessionCredentials == nil ||
159
			provider.expirationTimestamp == 0 ||
160
			provider.expirationTimestamp-time.Now().Unix() <= 180 {
161
			provider.sessionCredentials = sessionCredentials
162
163
			// 如果返回了过期时间,解析并缓存
164
			if sessionCredentials.Expiration != "" {
165
				expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
166
				if err2 != nil {
167
					// 如果解析失败,不设置过期时间,下次调用时重新获取
168
					provider.expirationTimestamp = 0
169
				} else {
170
					provider.lastUpdateTimestamp = time.Now().Unix()
171
					provider.expirationTimestamp = expirationTime.Unix()
172
				}
173
			} else {
174
				// 没有过期时间,下次调用时重新获取
175
				provider.expirationTimestamp = 0
176
			}
177
		}
178
		expirationTimestamp := provider.expirationTimestamp
179
		sessionCredentials = provider.sessionCredentials
180
		provider.mu.Unlock()
181
182
		// 如果设置了回调函数,则调用回调函数写回配置文件(在锁外执行)
183
		if provider.credentialUpdateCallback != nil {
184
			err1 := provider.credentialUpdateCallback(
185
				sessionCredentials.AccessKeyId,
186
				sessionCredentials.AccessKeySecret,
187
				sessionCredentials.SecurityToken,
188
				expirationTimestamp,
189
			)
190
			if err1 != nil {
191
				fmt.Printf("Warning: failed to update external credentials in config file: %v\n", err1)
192
			}
193
		}
194
	}
195
196
	// 使用读锁读取凭证
197
	provider.mu.RLock()
198
	cc = &Credentials{
199
		AccessKeyId:     provider.sessionCredentials.AccessKeyId,
200
		AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
201
		SecurityToken:   provider.sessionCredentials.SecurityToken,
202
		ProviderName:    provider.GetProviderName(),
203
	}
204
	provider.mu.RUnlock()
205
	return
206
}
207
208
func (provider *ExternalCredentialsProvider) GetProviderName() string {
209
	return "external"
210
}
211