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

Commitc6efbe0

Browse files
committed
feat: expose external scopes in OAuth2 metadata endpoints
Replace empty slice placeholders with rbac.ExternalScopeNames() topublish supported OAuth2 scopes from the curated RBAC scope catalog inboth authorization server and protected resource metadata endpoints.Update tests to verify proper scope exposure instead of empty arrays.
1 parent92a39d1 commitc6efbe0

File tree

18 files changed

+305
-35
lines changed

18 files changed

+305
-35
lines changed

‎coderd/apidoc/docs.go‎

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apikey.go‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,45 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
116116
TokenName:tokenName,
117117
}
118118

119+
iflen(createToken.AllowList)>0 {
120+
rbacAllowListElements:=make([]rbac.AllowListElement,0,len(createToken.AllowList))
121+
for_,t:=rangecreateToken.AllowList {
122+
entry,err:=rbac.NewAllowListElement(t.Type.String(),t.ID.String())
123+
iferr!=nil {
124+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
125+
Message:"Failed to create API key.",
126+
Detail:err.Error(),
127+
})
128+
return
129+
}
130+
rbacAllowListElements=append(rbacAllowListElements,entry)
131+
}
132+
133+
rbacAllowList,err:=rbac.NewAllowList(rbacAllowListElements,128)
134+
iferr!=nil {
135+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
136+
Message:"Failed to create API key.",
137+
Detail:err.Error(),
138+
})
139+
return
140+
}
141+
142+
dbAllowList:=make(database.AllowList,0,len(rbacAllowList))
143+
for_,e:=rangerbacAllowList {
144+
target,err:=database.NewAllowListTarget(e.Type,e.ID)
145+
iferr!=nil {
146+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
147+
Message:"Failed to create API key.",
148+
Detail:err.Error(),
149+
})
150+
return
151+
}
152+
dbAllowList=append(dbAllowList,target)
153+
}
154+
155+
params.AllowList=dbAllowList
156+
}
157+
119158
ifcreateToken.Lifetime!=0 {
120159
err:=api.validateAPIKeyLifetime(ctx,user.ID,createToken.Lifetime)
121160
iferr!=nil {

‎coderd/apikey/apikey.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type CreateParams struct {
3434
Scopes database.APIKeyScopes
3535
TokenNamestring
3636
RemoteAddrstring
37+
// AllowList is an optional, normalized allow-list
38+
// of resource type and uuid entries. If empty, defaults to wildcard.
39+
AllowList database.AllowList
3740
}
3841

3942
// Generate generates an API key, returning the key as a string as well as the
@@ -115,7 +118,7 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
115118
HashedSecret:hashed[:],
116119
LoginType:params.LoginType,
117120
Scopes:scopes,
118-
AllowList:database.AllowList{database.AllowListWildcard()},
121+
AllowList:params.AllowList,
119122
TokenName:params.TokenName,
120123
},token,nil
121124
}

‎coderd/database/dbgen/dbgen.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
186186
UpdatedAt:takeFirst(seed.UpdatedAt,dbtime.Now()),
187187
LoginType:takeFirst(seed.LoginType,database.LoginTypePassword),
188188
Scopes:takeFirstSlice([]database.APIKeyScope(seed.Scopes), []database.APIKeyScope{database.ApiKeyScopeCoderAll}),
189-
AllowList:takeFirstSlice(seed.AllowList, database.AllowList{database.AllowListWildcard()}),
189+
AllowList:takeFirstSlice(seed.AllowList, database.AllowList{}),
190190
TokenName:takeFirst(seed.TokenName),
191191
}
192192
for_,fn:=rangemunge {

‎coderd/database/modelmethods.go‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,29 @@ func (s APIKeyScopes) Expand() (rbac.Scope, error) {
203203
}
204204
}
205205

206+
// De-duplicate permissions across Site/Org/User
207+
dedup:=func(in []rbac.Permission) []rbac.Permission {
208+
iflen(in)==0 {
209+
returnin
210+
}
211+
seen:=make(map[string]struct{},len(in))
212+
out:=make([]rbac.Permission,0,len(in))
213+
for_,p:=rangein {
214+
key:=p.ResourceType+"\x00"+string(p.Action)+"\x00"+strconv.FormatBool(p.Negate)
215+
if_,ok:=seen[key];ok {
216+
continue
217+
}
218+
seen[key]=struct{}{}
219+
out=append(out,p)
220+
}
221+
returnout
222+
}
223+
merged.Site=dedup(merged.Site)
224+
fororgID,perms:=rangemerged.Org {
225+
merged.Org[orgID]=dedup(perms)
226+
}
227+
merged.User=dedup(merged.User)
228+
206229
ifallowAll||len(allowSet)==0 {
207230
merged.AllowIDList= []rbac.AllowListElement{rbac.AllowListAll()}
208231
}else {
@@ -228,6 +251,41 @@ func (s APIKeyScopes) Name() rbac.RoleIdentifier {
228251
return rbac.RoleIdentifier{Name:"scopes["+strings.Join(names,"+")+"]"}
229252
}
230253

254+
// APIKeyEffectiveScope merges expanded scopes with the API key's DB allow_list.
255+
// If the DB allow_list is a wildcard or empty, the merged scope's allow list is unchanged.
256+
// Otherwise, the DB allow_list overrides the merged AllowIDList to enforce the token's
257+
// resource scoping consistently across all permissions.
258+
typeAPIKeyEffectiveScopestruct {
259+
ScopesAPIKeyScopes
260+
AllowListAllowList
261+
}
262+
263+
func (eAPIKeyEffectiveScope)Name() rbac.RoleIdentifier {returne.Scopes.Name() }
264+
265+
func (eAPIKeyEffectiveScope)Expand() (rbac.Scope,error) {
266+
merged,err:=e.Scopes.Expand()
267+
iferr!=nil {
268+
return rbac.Scope{},err
269+
}
270+
iflen(e.AllowList)==0 {
271+
returnmerged,nil
272+
}
273+
274+
// If allow list contains a single wildcard (*:*), keep merged allow list as-is
275+
for_,entry:=rangee.AllowList {
276+
ifentry.Type.IsAny()&&entry.ID.IsAny() {
277+
returnmerged,nil
278+
}
279+
}
280+
281+
out:=make([]rbac.AllowListElement,0,len(e.AllowList))
282+
for_,t:=rangee.AllowList {
283+
out=append(out, rbac.AllowListElement{Type:t.Type.String(),ID:t.ID.String()})
284+
}
285+
merged.AllowIDList=out
286+
returnmerged,nil
287+
}
288+
231289
func (kAPIKey)RBACObject() rbac.Object {
232290
returnrbac.ResourceApiKey.WithIDString(k.ID).
233291
WithOwner(k.UserID.String())

‎coderd/database/models.go‎

Lines changed: 24 additions & 5 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/httpmw/authorize_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func addUser(t *testing.T, db database.Store, roles ...string) (database.User, s
173173
ExpiresAt:dbtime.Now().Add(time.Minute),
174174
LoginType:database.LoginTypePassword,
175175
Scopes: database.APIKeyScopes{database.ApiKeyScopeCoderAll},
176-
AllowList: database.AllowList{database.AllowListWildcard()},
176+
AllowList: database.AllowList{},
177177
IPAddress: pqtype.Inet{
178178
IPNet: net.IPNet{
179179
IP:net.ParseIP("0.0.0.0"),

‎coderd/httpmw/workspaceparam_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestWorkspaceParam(t *testing.T) {
6767
ExpiresAt:dbtime.Now().Add(time.Minute),
6868
LoginType:database.LoginTypePassword,
6969
Scopes: database.APIKeyScopes{database.ApiKeyScopeCoderAll},
70-
AllowList: database.AllowList{database.AllowListWildcard()},
70+
AllowList: database.AllowList{},
7171
IPAddress: pqtype.Inet{
7272
IPNet: net.IPNet{
7373
IP:net.IPv4(127,0,0,1),

‎coderd/oauth2_metadata_test.go‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stretchr/testify/require"
1111

1212
"github.com/coder/coder/v2/coderd/coderdtest"
13+
"github.com/coder/coder/v2/coderd/rbac"
1314
"github.com/coder/coder/v2/codersdk"
1415
"github.com/coder/coder/v2/testutil"
1516
)
@@ -46,6 +47,8 @@ func TestOAuth2AuthorizationServerMetadata(t *testing.T) {
4647
require.Contains(t,metadata.GrantTypesSupported,"authorization_code")
4748
require.Contains(t,metadata.GrantTypesSupported,"refresh_token")
4849
require.Contains(t,metadata.CodeChallengeMethodsSupported,"S256")
50+
// Supported scopes are published from the curated catalog
51+
require.Equal(t,rbac.ExternalScopeNames(),metadata.ScopesSupported)
4952
}
5053

5154
funcTestOAuth2ProtectedResourceMetadata(t*testing.T) {
@@ -80,7 +83,6 @@ func TestOAuth2ProtectedResourceMetadata(t *testing.T) {
8083
// RFC 6750 bearer tokens are now supported as fallback methods
8184
require.Contains(t,metadata.BearerMethodsSupported,"header")
8285
require.Contains(t,metadata.BearerMethodsSupported,"query")
83-
// ScopesSupported can be empty until scope system is implemented
84-
// Empty slice is marshaled as empty array, but can be nil when unmarshaled
85-
require.True(t,len(metadata.ScopesSupported)==0)
86+
// Supported scopes are published from the curated catalog
87+
require.Equal(t,rbac.ExternalScopeNames(),metadata.ScopesSupported)
8688
}

‎coderd/oauth2provider/metadata.go‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66

77
"github.com/coder/coder/v2/coderd/httpapi"
8+
"github.com/coder/coder/v2/coderd/rbac"
89
"github.com/coder/coder/v2/codersdk"
910
)
1011

@@ -13,15 +14,14 @@ func GetAuthorizationServerMetadata(accessURL *url.URL) http.HandlerFunc {
1314
returnfunc(rw http.ResponseWriter,r*http.Request) {
1415
ctx:=r.Context()
1516
metadata:= codersdk.OAuth2AuthorizationServerMetadata{
16-
Issuer:accessURL.String(),
17-
AuthorizationEndpoint:accessURL.JoinPath("/oauth2/authorize").String(),
18-
TokenEndpoint:accessURL.JoinPath("/oauth2/tokens").String(),
19-
RegistrationEndpoint:accessURL.JoinPath("/oauth2/register").String(),// RFC 7591
20-
ResponseTypesSupported: []string{"code"},
21-
GrantTypesSupported: []string{"authorization_code","refresh_token"},
22-
CodeChallengeMethodsSupported: []string{"S256"},
23-
// TODO: Implement scope system
24-
ScopesSupported: []string{},
17+
Issuer:accessURL.String(),
18+
AuthorizationEndpoint:accessURL.JoinPath("/oauth2/authorize").String(),
19+
TokenEndpoint:accessURL.JoinPath("/oauth2/tokens").String(),
20+
RegistrationEndpoint:accessURL.JoinPath("/oauth2/register").String(),// RFC 7591
21+
ResponseTypesSupported: []string{"code"},
22+
GrantTypesSupported: []string{"authorization_code","refresh_token"},
23+
CodeChallengeMethodsSupported: []string{"S256"},
24+
ScopesSupported:rbac.ExternalScopeNames(),
2525
TokenEndpointAuthMethodsSupported: []string{"client_secret_post"},
2626
}
2727
httpapi.Write(ctx,rw,http.StatusOK,metadata)
@@ -35,8 +35,7 @@ func GetProtectedResourceMetadata(accessURL *url.URL) http.HandlerFunc {
3535
metadata:= codersdk.OAuth2ProtectedResourceMetadata{
3636
Resource:accessURL.String(),
3737
AuthorizationServers: []string{accessURL.String()},
38-
// TODO: Implement scope system based on RBAC permissions
39-
ScopesSupported: []string{},
38+
ScopesSupported:rbac.ExternalScopeNames(),
4039
// RFC 6750 Bearer Token methods supported as fallback methods in api key middleware
4140
BearerMethodsSupported: []string{"header","query"},
4241
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp