Skip to content

Commit 5ff6543

Browse files
authored
chore: add credential checkParam field (#964)
If the check param changes, then the credential will be re-prompted and not used nor refreshed. Signed-off-by: Donnie Adams <[email protected]>
1 parent 7e668d5 commit 5ff6543

File tree

4 files changed

+85
-30
lines changed

4 files changed

+85
-30
lines changed

pkg/credentials/credential.go

+11-7
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ const (
2020
)
2121

2222
type Credential struct {
23-
Context string `json:"context"`
24-
ToolName string `json:"toolName"`
25-
Type CredentialType `json:"type"`
26-
Env map[string]string `json:"env"`
27-
Ephemeral bool `json:"ephemeral,omitempty"`
28-
ExpiresAt *time.Time `json:"expiresAt"`
29-
RefreshToken string `json:"refreshToken"`
23+
Context string `json:"context"`
24+
ToolName string `json:"toolName"`
25+
Type CredentialType `json:"type"`
26+
Env map[string]string `json:"env"`
27+
// If the CheckParam that is stored is different from the param on the tool,
28+
// then the credential will be re-authed as if it does not exist.
29+
CheckParam string `json:"checkParam"`
30+
Ephemeral bool `json:"ephemeral,omitempty"`
31+
ExpiresAt *time.Time `json:"expiresAt"`
32+
RefreshToken string `json:"refreshToken"`
3033
}
3134

3235
func (c Credential) IsExpired() bool {
@@ -82,6 +85,7 @@ func credentialFromDockerAuthConfig(authCfg types.AuthConfig) (Credential, error
8285
Context: ctx,
8386
ToolName: tool,
8487
Type: CredentialType(credType),
88+
CheckParam: cred.CheckParam,
8589
Env: cred.Env,
8690
ExpiresAt: cred.ExpiresAt,
8791
RefreshToken: cred.RefreshToken,

pkg/runner/runner.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env
780780

781781
var nearestExpiration *time.Time
782782
for _, ref := range credToolRefs {
783-
toolName, credentialAlias, args, err := types.ParseCredentialArgs(ref.Reference, callCtx.Input)
783+
toolName, credentialAlias, checkParam, args, err := types.ParseCredentialArgs(ref.Reference, callCtx.Input)
784784
if err != nil {
785785
return nil, fmt.Errorf("failed to parse credential tool %q: %w", ref.Reference, err)
786786
}
@@ -830,9 +830,10 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env
830830

831831
// If the credential doesn't already exist in the store, run the credential tool in order to get the value,
832832
// and save it in the store.
833-
if !exists || c.IsExpired() {
833+
if !exists || c.IsExpired() || checkParam != c.CheckParam {
834834
// If the existing credential is expired, we need to provide it to the cred tool through the environment.
835-
if exists && c.IsExpired() {
835+
// If the check parameter is different, then we don't refresh. We should re-auth below.
836+
if exists && c.IsExpired() && checkParam == c.CheckParam {
836837
refresh = true
837838
credJSON, err := json.Marshal(c)
838839
if err != nil {

pkg/types/credential_test.go

+44-8
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import (
99

1010
func TestParseCredentialArgs(t *testing.T) {
1111
tests := []struct {
12-
name string
13-
toolName string
14-
input string
15-
expectedName string
16-
expectedAlias string
17-
expectedArgs map[string]string
18-
wantErr bool
12+
name string
13+
toolName string
14+
input string
15+
expectedName string
16+
expectedAlias string
17+
expectedCheckParam string
18+
expectedArgs map[string]string
19+
wantErr bool
1920
}{
2021
{
2122
name: "empty",
@@ -94,6 +95,40 @@ func TestParseCredentialArgs(t *testing.T) {
9495
"arg2": "value2",
9596
},
9697
},
98+
{
99+
name: "tool name with check parameter",
100+
toolName: `myCredentialTool checked with myCheckParam`,
101+
expectedName: "myCredentialTool",
102+
expectedCheckParam: "myCheckParam",
103+
},
104+
{
105+
name: "tool name with alias and check parameter",
106+
toolName: `myCredentialTool as myAlias checked with myCheckParam`,
107+
expectedName: "myCredentialTool",
108+
expectedAlias: "myAlias",
109+
expectedCheckParam: "myCheckParam",
110+
},
111+
{
112+
name: "tool name with alias, check parameter, and args",
113+
toolName: `myCredentialTool as myAlias checked with myCheckParam with value1 as arg1 and value2 as arg2`,
114+
expectedName: "myCredentialTool",
115+
expectedAlias: "myAlias",
116+
expectedCheckParam: "myCheckParam",
117+
expectedArgs: map[string]string{
118+
"arg1": "value1",
119+
"arg2": "value2",
120+
},
121+
},
122+
{
123+
name: "check parameter without with",
124+
toolName: `myCredentialTool checked myCheckParam`,
125+
wantErr: true,
126+
},
127+
{
128+
name: "invalid check parameter",
129+
toolName: `myCredentialTool checked with`,
130+
wantErr: true,
131+
},
97132
{
98133
name: "tool name with alias but no 'as' (invalid)",
99134
toolName: "myCredentialTool myAlias",
@@ -136,7 +171,7 @@ func TestParseCredentialArgs(t *testing.T) {
136171

137172
for _, tt := range tests {
138173
t.Run(tt.name, func(t *testing.T) {
139-
originalName, alias, args, err := ParseCredentialArgs(tt.toolName, tt.input)
174+
originalName, alias, checkParam, args, err := ParseCredentialArgs(tt.toolName, tt.input)
140175
if tt.wantErr {
141176
require.Error(t, err, "expected an error but got none")
142177
return
@@ -145,6 +180,7 @@ func TestParseCredentialArgs(t *testing.T) {
145180
require.NoError(t, err, "did not expect an error but got one")
146181
require.Equal(t, tt.expectedName, originalName, "unexpected original name")
147182
require.Equal(t, tt.expectedAlias, alias, "unexpected alias")
183+
require.Equal(t, tt.expectedCheckParam, checkParam, "unexpected checkParam")
148184
require.Equal(t, len(tt.expectedArgs), len(args), "unexpected number of args")
149185

150186
for k, v := range tt.expectedArgs {

pkg/types/tool.go

+26-12
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ func SplitArg(hasArg string) (prefix, arg string) {
272272
// - toolName: "toolName with ${var1} as arg1 and ${var2} as arg2"
273273
// - input: `{"var1": "value1", "var2": "value2"}`
274274
// result: toolName, "", map[string]any{"arg1": "value1", "arg2": "value2"}, nil
275-
func ParseCredentialArgs(toolName string, input string) (string, string, map[string]any, error) {
275+
func ParseCredentialArgs(toolName string, input string) (string, string, string, map[string]any, error) {
276276
if toolName == "" {
277-
return "", "", nil, nil
277+
return "", "", "", nil, nil
278278
}
279279

280280
inputMap := make(map[string]any)
@@ -287,12 +287,12 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str
287287

288288
fields, err := shlex.Split(toolName)
289289
if err != nil {
290-
return "", "", nil, err
290+
return "", "", "", nil, err
291291
}
292292

293293
// If it's just the tool name, return it
294294
if len(fields) == 1 {
295-
return toolName, "", nil, nil
295+
return toolName, "", "", nil, nil
296296
}
297297

298298
// Next field is "as" if there is an alias, otherwise it should be "with"
@@ -301,25 +301,39 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str
301301
fields = fields[1:]
302302
if fields[0] == "as" {
303303
if len(fields) < 2 {
304-
return "", "", nil, fmt.Errorf("expected alias after 'as'")
304+
return "", "", "", nil, fmt.Errorf("expected alias after 'as'")
305305
}
306306
alias = fields[1]
307307
fields = fields[2:]
308308
}
309309

310310
if len(fields) == 0 { // Nothing left, so just return
311-
return originalName, alias, nil, nil
311+
return originalName, alias, "", nil, nil
312+
}
313+
314+
var checkParam string
315+
if fields[0] == "checked" {
316+
if len(fields) < 3 || fields[1] != "with" {
317+
return "", "", "", nil, fmt.Errorf("expected 'checked with some_value' but got %v", fields)
318+
}
319+
320+
checkParam = fields[2]
321+
fields = fields[3:]
322+
}
323+
324+
if len(fields) == 0 { // Nothing left, so just return
325+
return originalName, alias, checkParam, nil, nil
312326
}
313327

314328
// Next we should have "with" followed by the args
315329
if fields[0] != "with" {
316-
return "", "", nil, fmt.Errorf("expected 'with' but got %s", fields[0])
330+
return "", "", "", nil, fmt.Errorf("expected 'with' but got %s", fields[0])
317331
}
318332
fields = fields[1:]
319333

320334
// If there are no args, return an error
321335
if len(fields) == 0 {
322-
return "", "", nil, fmt.Errorf("expected args after 'with'")
336+
return "", "", "", nil, fmt.Errorf("expected args after 'with'")
323337
}
324338

325339
args := make(map[string]any)
@@ -332,22 +346,22 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str
332346
prev = "value"
333347
case "value":
334348
if field != "as" {
335-
return "", "", nil, fmt.Errorf("expected 'as' but got %s", field)
349+
return "", "", "", nil, fmt.Errorf("expected 'as' but got %s", field)
336350
}
337351
prev = "as"
338352
case "as":
339353
args[field] = argValue
340354
prev = "name"
341355
case "name":
342356
if field != "and" {
343-
return "", "", nil, fmt.Errorf("expected 'and' but got %s", field)
357+
return "", "", "", nil, fmt.Errorf("expected 'and' but got %s", field)
344358
}
345359
prev = "and"
346360
}
347361
}
348362

349363
if prev == "and" {
350-
return "", "", nil, fmt.Errorf("expected arg name after 'and'")
364+
return "", "", "", nil, fmt.Errorf("expected arg name after 'and'")
351365
}
352366

353367
// Check and see if any of the arg values are references to an input
@@ -360,7 +374,7 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str
360374
}
361375
}
362376

363-
return originalName, alias, args, nil
377+
return originalName, alias, checkParam, args, nil
364378
}
365379

366380
func (t Tool) GetToolRefsFromNames(names []string) (result []ToolReference, _ error) {

0 commit comments

Comments
 (0)