Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 22 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,17 @@ func (q *querier) DeleteAPIKeyByID(ctx context.Context, id string) error {
return deleteQ(q.log, q.auth, q.db.GetAPIKeyByID, q.db.DeleteAPIKeyByID)(ctx, id)
}

func (q *querier) DeleteAPIKeyByIDReturningID(ctx context.Context, id string) (string, error) {
Comment thread
BobbyHo marked this conversation as resolved.
Outdated
key, err := q.db.GetAPIKeyByID(ctx, id)
if err != nil {
return "", err
}
if err := q.authorizeContext(ctx, policy.ActionDelete, key); err != nil {
return "", err
}
return q.db.DeleteAPIKeyByIDReturningID(ctx, id)
}

func (q *querier) DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
// TODO: This is not 100% correct because it omits apikey IDs.
err := q.authorizeContext(ctx, policy.ActionDelete,
Expand Down Expand Up @@ -2314,6 +2325,17 @@ func (q *querier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.U
return q.db.DeleteOAuth2ProviderAppCodeByID(ctx, id)
}

func (q *querier) DeleteOAuth2ProviderAppCodeByIDReturningID(ctx context.Context, id uuid.UUID) (uuid.UUID, error) {
code, err := q.db.GetOAuth2ProviderAppCodeByID(ctx, id)
if err != nil {
return uuid.Nil, err
}
if err := q.authorizeContext(ctx, policy.ActionDelete, code); err != nil {
return uuid.Nil, err
}
return q.db.DeleteOAuth2ProviderAppCodeByIDReturningID(ctx, id)
}

func (q *querier) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
if err := q.authorizeContext(ctx, policy.ActionDelete,
rbac.ResourceOauth2AppCodeToken.WithOwner(arg.UserID.String())); err != nil {
Expand Down
15 changes: 15 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ func (s *MethodTestSuite) TestAPIKey() {
dbm.EXPECT().DeleteAPIKeyByID(gomock.Any(), key.ID).Return(nil).AnyTimes()
check.Args(key.ID).Asserts(key, policy.ActionDelete).Returns()
}))
s.Run("DeleteAPIKeyByIDReturningID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
key := testutil.Fake(s.T(), faker, database.APIKey{})
dbm.EXPECT().GetAPIKeyByID(gomock.Any(), key.ID).Return(key, nil).AnyTimes()
dbm.EXPECT().DeleteAPIKeyByIDReturningID(gomock.Any(), key.ID).Return(key.ID, nil).AnyTimes()
check.Args(key.ID).Asserts(key, policy.ActionDelete).Returns(key.ID)
}))
s.Run("DeleteExpiredAPIKeys", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
args := database.DeleteExpiredAPIKeysParams{
Before: time.Date(2025, 11, 21, 0, 0, 0, 0, time.UTC),
Expand Down Expand Up @@ -6001,6 +6007,15 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() {
})
check.Args(code.ID).Asserts(code, policy.ActionDelete)
}))
s.Run("DeleteOAuth2ProviderAppCodeByIDReturningID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{
AppID: app.ID,
UserID: user.ID,
})
check.Args(code.ID).Asserts(code, policy.ActionDelete).Returns(code.ID)
}))
s.Run("DeleteOAuth2ProviderAppCodesByAppAndUserID", s.Subtest(func(db database.Store, check *expects) {
dbtestutil.DisableForeignKeysAndTriggers(s.T(), db)
user := dbgen.User(s.T(), db, database.User{})
Expand Down
2 changes: 2 additions & 0 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,7 @@ func OAuth2ProviderAppCode(t testing.TB, db database.Store, seed database.OAuth2
CodeChallengeMethod: seed.CodeChallengeMethod,
StateHash: seed.StateHash,
RedirectUri: seed.RedirectUri,
Scope: seed.Scope,
})
require.NoError(t, err, "insert oauth2 app code")
return code
Expand All @@ -1805,6 +1806,7 @@ func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth
APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()),
UserID: takeFirst(seed.UserID, uuid.New()),
Audience: seed.Audience,
Scope: seed.Scope,
})
require.NoError(t, err, "insert oauth2 app token")
return token
Expand Down
16 changes: 16 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

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

30 changes: 30 additions & 0 deletions coderd/database/dbmock/dbmock.go

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

10 changes: 8 additions & 2 deletions coderd/database/dump.sql

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE oauth2_provider_app_codes DROP COLUMN scope;

ALTER TABLE oauth2_provider_app_tokens DROP COLUMN scope;
16 changes: 16 additions & 0 deletions coderd/database/migrations/000567_oauth2_scope_enforcement.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- The scope negotiated at /oauth2/authorize travels with the grant itself:
-- recorded on the code when it is issued, then carried onto the token it is
-- exchanged for so a refresh can narrow against what was actually granted
-- rather than against the app's current allowlist.
--
-- Both columns are nullable with no backfill. A NULL means "no scope was
-- recorded for this grant", which the token endpoint reads as unrestricted
-- access, so codes and tokens issued before this migration keep working.

ALTER TABLE oauth2_provider_app_codes ADD COLUMN scope text;

ALTER TABLE oauth2_provider_app_tokens ADD COLUMN scope text;

COMMENT ON COLUMN oauth2_provider_app_codes.scope IS 'Space-separated scope negotiated at authorization time. NULL means no scope was recorded and the exchanged token is unrestricted.';

COMMENT ON COLUMN oauth2_provider_app_tokens.scope IS 'Space-separated scope granted to this token. A refresh may narrow this but never widen it. NULL means no scope was recorded and the token is unrestricted.';
Comment thread
BobbyHo marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions coderd/database/models.go

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

6 changes: 6 additions & 0 deletions coderd/database/querier.go

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

Loading
Loading