Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 10 additions & 5 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5812,7 +5812,8 @@ func (s *MethodTestSuite) TestOAuth2ProviderApps() {
})
for i := 0; i < 5; i++ {
_ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
AppID: app.ID,
AppSecretID: uuid.NullUUID{UUID: secret.ID, Valid: true},
APIKeyID: key.ID,
UserID: user.ID,
HashPrefix: []byte(fmt.Sprintf("%d", i)),
Expand Down Expand Up @@ -6020,7 +6021,8 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
AppID: app.ID,
})
check.Args(database.InsertOAuth2ProviderAppTokenParams{
AppSecretID: secret.ID,
AppID: app.ID,
AppSecretID: uuid.NullUUID{UUID: secret.ID, Valid: true},
APIKeyID: key.ID,
UserID: user.ID,
}).Asserts(rbac.ResourceOauth2AppCodeToken.WithOwner(user.ID.String()), policy.ActionCreate)
Expand All @@ -6035,7 +6037,8 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
AppID: app.ID,
})
token := dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
AppID: app.ID,
AppSecretID: uuid.NullUUID{UUID: secret.ID, Valid: true},
APIKeyID: key.ID,
UserID: user.ID,
})
Expand All @@ -6051,7 +6054,8 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
AppID: app.ID,
})
token := dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
AppID: app.ID,
AppSecretID: uuid.NullUUID{UUID: secret.ID, Valid: true},
APIKeyID: key.ID,
UserID: user.ID,
})
Expand All @@ -6069,7 +6073,8 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
})
for i := 0; i < 5; i++ {
_ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
AppID: app.ID,
AppSecretID: uuid.NullUUID{UUID: secret.ID, Valid: true},
APIKeyID: key.ID,
UserID: user.ID,
HashPrefix: []byte(fmt.Sprintf("%d", i)),
Expand Down
7 changes: 6 additions & 1 deletion coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1789,13 +1789,18 @@ func OAuth2ProviderAppCode(t testing.TB, db database.Store, seed database.OAuth2
}

func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth2ProviderAppToken) database.OAuth2ProviderAppToken {
require.NotEqual(t, uuid.Nil, seed.AppID, "An app id is required to use 'dbgen.OAuth2ProviderAppToken', use 'dbgen.OAuth2ProviderApp'.")
token, err := db.InsertOAuth2ProviderAppToken(genCtx, database.InsertOAuth2ProviderAppTokenParams{
ID: takeFirst(seed.ID, uuid.New()),
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()),
HashPrefix: takeFirstSlice(seed.HashPrefix, []byte("prefix")),
RefreshHash: takeFirstSlice(seed.RefreshHash, []byte("hashed-secret")),
AppSecretID: takeFirst(seed.AppSecretID, uuid.New()),
AppID: seed.AppID,
// Public (secretless) clients reference no secret, so a zero-value
// NullUUID is passed through as NULL rather than defaulted. takeFirst
// cannot express that, since NULL is its "unset" sentinel.
AppSecretID: seed.AppSecretID,
APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()),
UserID: takeFirst(seed.UserID, uuid.New()),
Audience: seed.Audience,
Expand Down
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.

1 change: 1 addition & 0 deletions coderd/database/foreign_key_constraint.go

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,8 @@
-- Reverse of up-step 4: restore the original NOT NULL. Fails if any
-- public-client token (app_secret_id IS NULL) exists. Revoke every
-- outstanding public-client session before rolling this migration back.
ALTER TABLE oauth2_provider_app_tokens ALTER COLUMN app_secret_id SET NOT NULL;

-- Reverse of up-step 3/1: drop the new column and its FK entirely.
ALTER TABLE oauth2_provider_app_tokens DROP CONSTRAINT oauth2_provider_app_tokens_app_id_fkey;
Comment thread
BobbyHo marked this conversation as resolved.
ALTER TABLE oauth2_provider_app_tokens DROP COLUMN app_id;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Public (secretless, PKCE-only) OAuth2 clients have no client_secret, so
-- their tokens have nothing to put in app_secret_id. Add a direct app_id
-- column so token ownership checks (e.g. revocation) don't have to join
-- through a secret that may not exist, then loosen app_secret_id's NOT NULL.

-- Step 1: add app_id as nullable first.
ALTER TABLE oauth2_provider_app_tokens ADD COLUMN app_id uuid;
Comment thread
BobbyHo marked this conversation as resolved.

-- Step 2: backfill every existing row via the only path available today
-- (the same join revoke.go currently does at request time).
UPDATE oauth2_provider_app_tokens t
SET app_id = s.app_id
FROM oauth2_provider_app_secrets s
WHERE t.app_secret_id = s.id;

-- Step 3: now that every row has a value, constrain it.
ALTER TABLE oauth2_provider_app_tokens ALTER COLUMN app_id SET NOT NULL;
ALTER TABLE oauth2_provider_app_tokens
ADD CONSTRAINT oauth2_provider_app_tokens_app_id_fkey
FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
Comment thread
BobbyHo marked this conversation as resolved.

-- Step 4: only now loosen app_secret_id, since every row already has a
-- reliable app_id to fall back on before this runs.
ALTER TABLE oauth2_provider_app_tokens ALTER COLUMN app_secret_id DROP NOT NULL;

COMMENT ON COLUMN oauth2_provider_app_tokens.app_id IS 'Denormalized app ID so ownership checks (e.g. revocation) do not need to join through app_secret_id, which is NULL for public clients.';
88 changes: 88 additions & 0 deletions coderd/database/migrations/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2413,3 +2413,91 @@ func TestMigration000556UserSecretsEnabled(t *testing.T) {
"secret with both targets empty should be flipped to disabled "+
"to preserve the previous implicit-skip behavior")
}

// TestMigration000562OAuth2PublicClientTokensBackfill seeds a pre-migration
// oauth2_provider_app_tokens row (the only shape that could exist before this
// migration, since app_secret_id was NOT NULL) and asserts that the new app_id
// column is backfilled from the existing app_secret_id -> app_id join, and
// that app_secret_id becomes nullable afterward.
func TestMigration000562OAuth2PublicClientTokensBackfill(t *testing.T) {
t.Parallel()

const priorMigrationVersion = 561

sqlDB := testSQLDB(t)

next, err := migrations.Stepper(sqlDB)
require.NoError(t, err)
for {
version, more, err := next()
require.NoError(t, err)
if !more || version == priorMigrationVersion {
break
}
}

ctx := testutil.Context(t, testutil.WaitSuperLong)
now := time.Now().UTC().Truncate(time.Microsecond)

userID := uuid.New()
appID := uuid.New()
secretID := uuid.New()
tokenID := uuid.New()
const apiKeyID = "test562apikeyid"

tx, err := sqlDB.BeginTx(ctx, nil)
require.NoError(t, err)
defer tx.Rollback()

_, err = tx.ExecContext(ctx, `
INSERT INTO users (id, username, email, hashed_password, created_at, updated_at, status, rbac_roles, login_type)
VALUES ($1, 'test-user-562', '[email protected]', ''::bytea, $2, $2, 'active', '{}', 'password')
`, userID, now)
require.NoError(t, err)

_, err = tx.ExecContext(ctx, `
INSERT INTO api_keys (id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, scopes, allow_list)
VALUES ($1, ''::bytea, $2, $3, $3, $3, $3, 'oauth2_provider_app', '{}', '{*}')
`, apiKeyID, userID, now)
require.NoError(t, err)

_, err = tx.ExecContext(ctx, `
INSERT INTO oauth2_provider_apps (id, created_at, updated_at, name, icon, callback_url)
VALUES ($1, $2, $2, 'test-app-562', '', 'http://localhost/callback')
`, appID, now)
require.NoError(t, err)

_, err = tx.ExecContext(ctx, `
INSERT INTO oauth2_provider_app_secrets (id, created_at, hashed_secret, display_secret, app_id, secret_prefix)
VALUES ($1, $2, ''::bytea, '****1234', $3, 'prefix562'::bytea)
`, secretID, now, appID)
require.NoError(t, err)

_, err = tx.ExecContext(ctx, `
INSERT INTO oauth2_provider_app_tokens (id, created_at, expires_at, hash_prefix, refresh_hash, app_secret_id, api_key_id, user_id)
VALUES ($1, $2, $3, 'prefix562'::bytea, ''::bytea, $4, $5, $6)
`, tokenID, now, now.Add(time.Hour), secretID, apiKeyID, userID)
require.NoError(t, err)

require.NoError(t, tx.Commit())

migrationSQL, err := os.ReadFile("000562_oauth2_public_client_tokens.up.sql")
require.NoError(t, err)
_, err = sqlDB.ExecContext(ctx, string(migrationSQL))
require.NoError(t, err)

var backfilledAppID uuid.UUID
err = sqlDB.QueryRowContext(ctx,
`SELECT app_id FROM oauth2_provider_app_tokens WHERE id = $1`, tokenID,
).Scan(&backfilledAppID)
require.NoError(t, err)
require.Equal(t, appID, backfilledAppID, "app_id should be backfilled from app_secret_id's existing join")

var isNullable string
err = sqlDB.QueryRowContext(ctx, `
SELECT is_nullable FROM information_schema.columns
WHERE table_name = 'oauth2_provider_app_tokens' AND column_name = 'app_secret_id'
`).Scan(&isNullable)
require.NoError(t, err)
require.Equal(t, "YES", isNullable, "app_secret_id should be nullable after the migration")
}
8 changes: 5 additions & 3 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