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 cleanup for expired OAuth2 provider app codes and tokens#18825

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

Open
ThomasK33 wants to merge1 commit intothomask33/07-08-feat_replace_callback_url_with_redirect_uris_for_oauth2_rfc_6749_compliance
base:thomask33/07-08-feat_replace_callback_url_with_redirect_uris_for_oauth2_rfc_6749_compliance
Choose a base branch
Loading
fromthomask33/07-10-feat_add_cleanup_for_expired_oauth2_provider_app_codes_and_tokens
Open
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
16 changes: 16 additions & 0 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1479,6 +1479,22 @@ func (q *querier) DeleteCustomRole(ctx context.Context, arg database.DeleteCusto
return q.db.DeleteCustomRole(ctx, arg)
}

func (q *querier) DeleteExpiredOAuth2ProviderAppCodes(ctx context.Context) error {
// System operation - only system can clean up expired authorization codes
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
return err
}
return q.db.DeleteExpiredOAuth2ProviderAppCodes(ctx)
}

func (q *querier) DeleteExpiredOAuth2ProviderAppTokens(ctx context.Context) error {
// System operation - only system can clean up expired access tokens
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
return err
}
return q.db.DeleteExpiredOAuth2ProviderAppTokens(ctx)
}

func (q *querier) DeleteExpiredOAuth2ProviderDeviceCodes(ctx context.Context) error {
// System operation - only system can clean up expired device codes
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
Expand Down
6 changes: 6 additions & 0 deletionscoderd/database/dbauthz/dbauthz_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5569,6 +5569,9 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() {
})
check.Args(code.SecretPrefix).Asserts(code, policy.ActionUpdate).Returns(code)
}))
s.Run("DeleteExpiredOAuth2ProviderAppCodes", s.Subtest(func(db database.Store, check *expects) {
check.Args().Asserts(rbac.ResourceSystem, policy.ActionDelete)
}))
}

func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
Expand DownExpand Up@@ -5642,6 +5645,9 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
UserID: user.ID,
}).Asserts(rbac.ResourceOauth2AppCodeToken.WithOwner(user.ID.String()), policy.ActionDelete)
}))
s.Run("DeleteExpiredOAuth2ProviderAppTokens", s.Subtest(func(db database.Store, check *expects) {
check.Args().Asserts(rbac.ResourceSystem, policy.ActionDelete)
}))
}

func (s *MethodTestSuite) TestOAuth2ProviderDeviceCodes() {
Expand Down
14 changes: 14 additions & 0 deletionscoderd/database/dbmetrics/querymetrics.go
View file
Open in desktop

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

28 changes: 28 additions & 0 deletionscoderd/database/dbmock/dbmock.go
View file
Open in desktop

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

9 changes: 9 additions & 0 deletionscoderd/database/dbpurge/dbpurge.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,15 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, clk quartz.
if err := tx.DeleteOldNotificationMessages(ctx); err != nil {
return xerrors.Errorf("failed to delete old notification messages: %w", err)
}
if err := tx.DeleteExpiredOAuth2ProviderAppCodes(ctx); err != nil {
return xerrors.Errorf("failed to delete expired oauth2 provider app codes: %w", err)
}
if err := tx.DeleteExpiredOAuth2ProviderAppTokens(ctx); err != nil {
return xerrors.Errorf("failed to delete expired oauth2 provider app tokens: %w", err)
}
if err := tx.DeleteExpiredOAuth2ProviderDeviceCodes(ctx); err != nil {
return xerrors.Errorf("failed to delete expired oauth2 provider device codes: %w", err)
}

deleteOldAuditLogConnectionEventsBefore := start.Add(-maxAuditLogConnectionEventAge)
if err := tx.DeleteOldAuditLogConnectionEvents(ctx, database.DeleteOldAuditLogConnectionEventsParams{
Expand Down
213 changes: 213 additions & 0 deletionscoderd/database/dbpurge/dbpurge_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -635,3 +635,216 @@ func TestDeleteOldAuditLogConnectionEventsLimit(t *testing.T) {

require.Len(t, logs, 0)
}

//nolint:paralleltest // It uses LockIDDBPurge.
func TestDeleteExpiredOAuth2ProviderAppCodes(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

clk := quartz.NewMock(t)
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})

now := dbtime.Now()
clk.Set(now).MustWait(ctx)

// Create test data
user := dbgen.User(t, db, database.User{})
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
Name: fmt.Sprintf("test-codes-%d", time.Now().UnixNano()),
})

// Create expired authorization code (should be deleted)
expiredCode := dbgen.OAuth2ProviderAppCode(t, db, database.OAuth2ProviderAppCode{
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
AppID: app.ID,
UserID: user.ID,
SecretPrefix: []byte(fmt.Sprintf("expired-%d", time.Now().UnixNano())),
})

// Create non-expired authorization code (should be retained)
validCode := dbgen.OAuth2ProviderAppCode(t, db, database.OAuth2ProviderAppCode{
ExpiresAt: now.Add(1 * time.Hour), // Expires in 1 hour
AppID: app.ID,
UserID: user.ID,
SecretPrefix: []byte(fmt.Sprintf("valid-%d", time.Now().UnixNano())),
})

// Verify codes exist initially
_, err := db.GetOAuth2ProviderAppCodeByID(ctx, expiredCode.ID)
require.NoError(t, err)
_, err = db.GetOAuth2ProviderAppCodeByID(ctx, validCode.ID)
require.NoError(t, err)

// Run cleanup
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, clk)
defer closer.Close()
<-done

// Verify expired code is deleted
_, err = db.GetOAuth2ProviderAppCodeByID(ctx, expiredCode.ID)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)

// Verify non-expired code is retained
_, err = db.GetOAuth2ProviderAppCodeByID(ctx, validCode.ID)
require.NoError(t, err)
}

//nolint:paralleltest // It uses LockIDDBPurge.
func TestDeleteExpiredOAuth2ProviderAppTokens(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

clk := quartz.NewMock(t)
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})

now := dbtime.Now()
clk.Set(now).MustWait(ctx)

// Create test data
user := dbgen.User(t, db, database.User{})
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
Name: fmt.Sprintf("test-tokens-%d", time.Now().UnixNano()),
})
appSecret := dbgen.OAuth2ProviderAppSecret(t, db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})

// Create API keys for the tokens
expiredAPIKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: now.Add(-1 * time.Hour),
})
validAPIKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: now.Add(24 * time.Hour), // Valid for 24 hours
})

// Create expired access token (should be deleted)
expiredToken := dbgen.OAuth2ProviderAppToken(t, db, database.OAuth2ProviderAppToken{
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
AppSecretID: appSecret.ID,
APIKeyID: expiredAPIKey.ID,
UserID: user.ID,
HashPrefix: []byte(fmt.Sprintf("expired-%d", time.Now().UnixNano())),
})

// Create non-expired access token (should be retained)
validToken := dbgen.OAuth2ProviderAppToken(t, db, database.OAuth2ProviderAppToken{
ExpiresAt: now.Add(1 * time.Hour), // Expires in 1 hour
AppSecretID: appSecret.ID,
APIKeyID: validAPIKey.ID,
UserID: user.ID,
HashPrefix: []byte(fmt.Sprintf("valid-%d", time.Now().UnixNano())),
})

// Verify tokens exist initially
_, err := db.GetOAuth2ProviderAppTokenByPrefix(ctx, expiredToken.HashPrefix)
require.NoError(t, err)
_, err = db.GetOAuth2ProviderAppTokenByPrefix(ctx, validToken.HashPrefix)
require.NoError(t, err)

// Run cleanup
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, clk)
defer closer.Close()
<-done

// Verify expired token is deleted
_, err = db.GetOAuth2ProviderAppTokenByPrefix(ctx, expiredToken.HashPrefix)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)

// Verify non-expired token is retained
_, err = db.GetOAuth2ProviderAppTokenByPrefix(ctx, validToken.HashPrefix)
require.NoError(t, err)
}

//nolint:paralleltest // It uses LockIDDBPurge.
func TestDeleteExpiredOAuth2ProviderDeviceCodes(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

clk := quartz.NewMock(t)
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})

now := dbtime.Now()
clk.Set(now).MustWait(ctx)

// Create test data
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
Name: fmt.Sprintf("test-device-%d", time.Now().UnixNano()),
})

nanoTime := time.Now().UnixNano()

// Create expired device code with pending status (should be deleted)
expiredPendingCode := dbgen.OAuth2ProviderDeviceCode(t, db, database.OAuth2ProviderDeviceCode{
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
ClientID: app.ID,
Status: database.OAuth2DeviceStatusPending,
DeviceCodePrefix: fmt.Sprintf("EP%06d", nanoTime%1000000),
UserCode: fmt.Sprintf("EP%06d", nanoTime%1000000),
DeviceCodeHash: fmt.Appendf(nil, "hash-exp-pending-%d", nanoTime),
})

// Create non-expired device code with pending status (should be retained)
validPendingCode := dbgen.OAuth2ProviderDeviceCode(t, db, database.OAuth2ProviderDeviceCode{
ExpiresAt: now.Add(1 * time.Hour), // Expires in 1 hour
ClientID: app.ID,
Status: database.OAuth2DeviceStatusPending,
DeviceCodePrefix: fmt.Sprintf("VP%06d", (nanoTime+1)%1000000),
UserCode: fmt.Sprintf("VP%06d", (nanoTime+1)%1000000),
DeviceCodeHash: fmt.Appendf(nil, "hash-val-pending-%d", nanoTime+1),
})

// Create expired device code with authorized status (should be deleted - all expired codes are deleted)
expiredAuthorizedCode := dbgen.OAuth2ProviderDeviceCode(t, db, database.OAuth2ProviderDeviceCode{
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
ClientID: app.ID,
DeviceCodePrefix: fmt.Sprintf("EA%06d", (nanoTime+2)%1000000),
UserCode: fmt.Sprintf("EA%06d", (nanoTime+2)%1000000),
DeviceCodeHash: fmt.Appendf(nil, "hash-exp-auth-%d", nanoTime+2),
})

// Create a user and authorize the device code
user := dbgen.User(t, db, database.User{})
expiredAuthorizedCode, err := db.UpdateOAuth2ProviderDeviceCodeAuthorization(ctx, database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams{
ID: expiredAuthorizedCode.ID,
UserID: uuid.NullUUID{UUID: user.ID, Valid: true},
Status: database.OAuth2DeviceStatusAuthorized,
})
require.NoError(t, err)

// Verify device codes exist initially
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredPendingCode.ID)
require.NoError(t, err)
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, validPendingCode.ID)
require.NoError(t, err)
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredAuthorizedCode.ID)
require.NoError(t, err)

// Run cleanup
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, clk)
defer closer.Close()
<-done

// Verify expired pending device code is deleted
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredPendingCode.ID)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)

// Verify non-expired pending device code is retained
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, validPendingCode.ID)
require.NoError(t, err)

// Verify expired authorized device code is deleted (all expired codes are deleted)
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredAuthorizedCode.ID)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)
}
2 changes: 2 additions & 0 deletionscoderd/database/querier.go
View file
Open in desktop

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

22 changes: 21 additions & 1 deletioncoderd/database/queries.sql.go
View file
Open in desktop

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

10 changes: 9 additions & 1 deletioncoderd/database/queries/oauth2.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -305,7 +305,15 @@ DELETE FROM oauth2_provider_device_codes WHERE id = $1;

-- name: DeleteExpiredOAuth2ProviderDeviceCodes :exec
DELETE FROM oauth2_provider_device_codes
WHERE expires_at < NOW() AND status = 'pending';
WHERE expires_at < NOW();

-- name: DeleteExpiredOAuth2ProviderAppCodes :exec
DELETE FROM oauth2_provider_app_codes
WHERE expires_at < NOW();

-- name: DeleteExpiredOAuth2ProviderAppTokens :exec
DELETE FROM oauth2_provider_app_tokens
WHERE expires_at < NOW();

-- name: GetOAuth2ProviderDeviceCodesByClientID :many
SELECT * FROM oauth2_provider_device_codes
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp