Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add multi-scope support to API keys#19917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletionMakefile
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -872,7 +872,7 @@ codersdk/rbacresources_gen.go: scripts/typegen/codersdk.gotmpl scripts/typegen/m
touch "$@"

codersdk/apikey_scopes_gen.go: scripts/apikeyscopesgen/main.go coderd/rbac/scopes_catalog.go coderd/rbac/scopes.go
# Generate SDK constants forpublic low-level API key scopes.
# Generate SDK constants forexternal API key scopes.
go run ./scripts/apikeyscopesgen> /tmp/apikey_scopes_gen.go
mv /tmp/apikey_scopes_gen.go codersdk/apikey_scopes_gen.go
touch"$@"
Expand Down
29 changes: 25 additions & 4 deletionscoderd/apidoc/docs.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

29 changes: 25 additions & 4 deletionscoderd/apidoc/swagger.json
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

31 changes: 26 additions & 5 deletionscoderd/apikey.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,18 +67,39 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
}

// Map and validate requested scope.
// Accept special scopes (all, application_connect) and curated public low-level scopes.
scopes := database.APIKeyScopes{database.APIKeyScopeAll}
if createToken.Scope != "" {
// Accept legacy special scopes (all, application_connect) and external scopes.
// Default to coder:all scopes for backward compatibility.
scopes := database.APIKeyScopes{database.ApiKeyScopeCoderAll}
if len(createToken.Scopes) > 0 {
scopes = make(database.APIKeyScopes, 0, len(createToken.Scopes))
for _, s := range createToken.Scopes {
name := string(s)
if !rbac.IsExternalScope(rbac.ScopeName(name)) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to create API key.",
Detail: fmt.Sprintf("invalid or unsupported API key scope: %q", name),
})
return
}
scopes = append(scopes, database.APIKeyScope(name))
}
} else if string(createToken.Scope) != "" {
name := string(createToken.Scope)
if !rbac.IsExternalScope(rbac.ScopeName(name)) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to create API key.",
Detail: fmt.Sprintf("invalid API key scope: %q", name),
Detail: fmt.Sprintf("invalidor unsupportedAPI key scope: %q", name),
})
return
}
scopes = database.APIKeyScopes{database.APIKeyScope(name)}
switch name {
case "all":
scopes = database.APIKeyScopes{database.ApiKeyScopeCoderAll}
case "application_connect":
scopes = database.APIKeyScopes{database.ApiKeyScopeCoderApplicationConnect}
default:
scopes = database.APIKeyScopes{database.APIKeyScope(name)}
}
}

tokenName := namesgenerator.GetRandomName(1)
Expand Down
19 changes: 14 additions & 5 deletionscoderd/apikey/apikey.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,13 +25,12 @@ type CreateParams struct {
// Optional.
ExpiresAt time.Time
LifetimeSeconds int64

// Scope is legacy single-scope input kept for backward compatibility.
//
// Deprecated:Prefer Scopesfor new code.
// Deprecated:use Scopesinstead.
Scope database.APIKeyScope
// Scopes is the full list of scopes to attach to the key.
// If empty and Scope is set, the generator will use [Scope].
// If both are empty, the generator will default to [APIKeyScopeAll].
Scopes database.APIKeyScopes
TokenName string
RemoteAddr string
Expand DownExpand Up@@ -74,9 +73,19 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
case len(params.Scopes) > 0:
scopes = params.Scopes
case params.Scope != "":
scopes = database.APIKeyScopes{params.Scope}
var scope database.APIKeyScope
switch params.Scope {
case "all":
scope = database.ApiKeyScopeCoderAll
case "application_connect":
scope = database.ApiKeyScopeCoderApplicationConnect
default:
scope = params.Scope
}
scopes = database.APIKeyScopes{scope}
default:
scopes = database.APIKeyScopes{database.APIKeyScopeAll}
// Default to coder:all scope for backward compatibility.
scopes = database.APIKeyScopes{database.ApiKeyScopeCoderAll}
}

for _, s := range scopes {
Expand Down
10 changes: 5 additions & 5 deletionscoderd/apikey/apikey_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ func TestGenerate(t *testing.T) {
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
Scope: database.ApiKeyScopeCoderApplicationConnect,
},
},
{
Expand All@@ -62,7 +62,7 @@ func TestGenerate(t *testing.T) {
ExpiresAt: time.Time{},
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
Scope: database.ApiKeyScopeCoderApplicationConnect,
},
},
{
Expand All@@ -75,7 +75,7 @@ func TestGenerate(t *testing.T) {
ExpiresAt: time.Time{},
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
Scope: database.ApiKeyScopeCoderApplicationConnect,
},
},
{
Expand All@@ -88,7 +88,7 @@ func TestGenerate(t *testing.T) {
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "",
Scope: database.APIKeyScopeApplicationConnect,
Scope: database.ApiKeyScopeCoderApplicationConnect,
},
},
{
Expand DownExpand Up@@ -161,7 +161,7 @@ func TestGenerate(t *testing.T) {
if tc.params.Scope != "" {
assert.True(t, key.Scopes.Has(tc.params.Scope))
} else {
assert.True(t, key.Scopes.Has(database.APIKeyScopeAll))
assert.True(t, key.Scopes.Has(database.ApiKeyScopeCoderAll))
}

if tc.params.TokenName != "" {
Expand Down
19 changes: 18 additions & 1 deletioncoderd/apikey_scopes_validation_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,8 @@ func TestTokenCreation_ScopeValidation(t *testing.T) {
{name:"AllowsPublicLowLevelScope",scope:"workspace:read",wantErr:false},
{name:"RejectsInternalOnlyScope",scope:"debug_info:read",wantErr:true},
{name:"AllowsLegacyScopes",scope:"application_connect",wantErr:false},
{name:"AllowsCanonicalSpecialScope",scope:"all",wantErr:false},
{name:"AllowsLegacyScopes2",scope:"all",wantErr:false},
{name:"AllowsCanonicalSpecialScope",scope:"coder:all",wantErr:false},
}

for_,tc:=rangecases {
Expand All@@ -42,6 +43,22 @@ func TestTokenCreation_ScopeValidation(t *testing.T) {
}
require.NoError(t,err)
require.NotEmpty(t,resp.Key)

// Fetch and verify the stored scopes match expectation.
keys,err:=client.Tokens(ctx,codersdk.Me, codersdk.TokensFilter{})
require.NoError(t,err)
require.Len(t,keys,1)

// Normalize legacy singular scopes to canonical coder:* values.
expected:=tc.scope
switchtc.scope {
casecodersdk.APIKeyScopeAll:
expected=codersdk.APIKeyScopeCoderAll
casecodersdk.APIKeyScopeApplicationConnect:
expected=codersdk.APIKeyScopeCoderApplicationConnect
}

require.Contains(t,keys[0].Scopes,expected)
})
}
}
48 changes: 48 additions & 0 deletionscoderd/apikey_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,54 @@ func TestTokenScoped(t *testing.T) {
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
}

// Ensure backward-compat: when a token is created using the legacy singular
// scope names ("all" or "application_connect"), the API returns the same
// legacy value in the deprecated singular Scope field while also supporting
// the new multi-scope field.
func TestTokenLegacySingularScopeCompat(t *testing.T) {
t.Parallel()

cases := []struct {
name string
scope codersdk.APIKeyScope
scopes []codersdk.APIKeyScope
}{
{
name: "all",
scope: codersdk.APIKeyScopeAll,
scopes: []codersdk.APIKeyScope{codersdk.APIKeyScopeCoderAll},
},
{
name: "application_connect",
scope: codersdk.APIKeyScopeApplicationConnect,
scopes: []codersdk.APIKeyScope{codersdk.APIKeyScopeCoderApplicationConnect},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(t.Context(), testutil.WaitLong)
defer cancel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)

// Create with legacy singular scope.
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
Scope: tc.scope,
})
require.NoError(t, err)

// Read back and ensure the deprecated singular field matches exactly.
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.Len(t, keys, 1)
require.Equal(t, tc.scope, keys[0].Scope)
require.ElementsMatch(t, keys[0].Scopes, tc.scopes)
})
}
}

func TestUserSetTokenDuration(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp