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

Commit4ad81e8

Browse files
committed
chore: add OAuth2 device flow test scripts
Change-Id: Ic232851727e683ab3d8b7ce970c505588da2f827Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent13c50bd commit4ad81e8

File tree

43 files changed

+1271
-723
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1271
-723
lines changed

‎.claude/scripts/format.sh‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,30 +101,36 @@ fi
101101
# Get the file extension to determine the appropriate formatter
102102
file_ext="${file_path##*.}"
103103

104+
# Helper function to run formatter and handle errors
105+
run_formatter() {
106+
local target="$1"
107+
local file_type="$2"
108+
109+
if! make FILE="$file_path""$target";then
110+
echo"Error: Failed to format$file_type file:$file_path">&2
111+
exit 2
112+
fi
113+
echo"✓ Formatted$file_type file:$file_path"
114+
}
104115
# Change to the project root directory (where the Makefile is located)
105116
cd"$(dirname"$0")/../.."
106117

107118
# Call the appropriate Makefile target based on file extension
108119
case"$file_ext"in
109120
go)
110-
make fmt/go FILE="$file_path"
111-
echo"✓ Formatted Go file:$file_path"
121+
run_formatter"fmt/go""Go"
112122
;;
113123
js | jsx | ts | tsx)
114-
make fmt/ts FILE="$file_path"
115-
echo"✓ Formatted TypeScript/JavaScript file:$file_path"
124+
run_formatter"fmt/ts""TypeScript/JavaScript"
116125
;;
117126
tf | tfvars)
118-
make fmt/terraform FILE="$file_path"
119-
echo"✓ Formatted Terraform file:$file_path"
127+
run_formatter"fmt/terraform""Terraform"
120128
;;
121129
sh)
122-
make fmt/shfmt FILE="$file_path"
123-
echo"✓ Formatted shell script:$file_path"
130+
run_formatter"fmt/shfmt""shell script"
124131
;;
125132
md)
126-
make fmt/markdown FILE="$file_path"
127-
echo"✓ Formatted Markdown file:$file_path"
133+
run_formatter"fmt/markdown""Markdown"
128134
;;
129135
*)
130136
echo"No formatter available for file extension:$file_ext"

‎coderd/apidoc/docs.go‎

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

‎coderd/apidoc/swagger.json‎

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

‎coderd/audit/diff.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Auditable interface {
2424
database.NotificationsSettings|
2525
database.OAuth2ProviderApp|
2626
database.OAuth2ProviderAppSecret|
27+
database.OAuth2ProviderDeviceCode|
2728
database.PrebuildsSettings|
2829
database.CustomRole|
2930
database.AuditableOrganizationMember|

‎coderd/audit/request.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ func ResourceTarget[T Auditable](tgt T) string {
117117
returntyped.Name
118118
case database.OAuth2ProviderAppSecret:
119119
returntyped.DisplaySecret
120+
case database.OAuth2ProviderDeviceCode:
121+
returntyped.UserCode
120122
case database.CustomRole:
121123
returntyped.Name
122124
case database.AuditableOrganizationMember:
@@ -179,6 +181,8 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
179181
returntyped.ID
180182
case database.OAuth2ProviderAppSecret:
181183
returntyped.ID
184+
case database.OAuth2ProviderDeviceCode:
185+
returntyped.ID
182186
case database.CustomRole:
183187
returntyped.ID
184188
case database.AuditableOrganizationMember:
@@ -232,6 +236,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
232236
returndatabase.ResourceTypeOauth2ProviderApp
233237
case database.OAuth2ProviderAppSecret:
234238
returndatabase.ResourceTypeOauth2ProviderAppSecret
239+
case database.OAuth2ProviderDeviceCode:
240+
returndatabase.ResourceTypeOauth2ProviderDeviceCode
235241
case database.CustomRole:
236242
returndatabase.ResourceTypeCustomRole
237243
case database.AuditableOrganizationMember:
@@ -288,6 +294,8 @@ func ResourceRequiresOrgID[T Auditable]() bool {
288294
returnfalse
289295
case database.OAuth2ProviderAppSecret:
290296
returnfalse
297+
case database.OAuth2ProviderDeviceCode:
298+
returnfalse
291299
case database.CustomRole:
292300
returntrue
293301
case database.AuditableOrganizationMember:

‎coderd/coderd.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,7 @@ func New(options *Options) *API {
993993
r.Route("/device",func(r chi.Router) {
994994
r.Post("/",api.postOAuth2DeviceAuthorization())// RFC 8628 compliant endpoint
995995
r.Route("/verify",func(r chi.Router) {
996-
r.Use(apiKeyMiddleware)
996+
r.Use(apiKeyMiddlewareRedirect)
997997
r.Get("/",api.getOAuth2DeviceVerification())
998998
r.Post("/",api.postOAuth2DeviceVerification())
999999
})

‎coderd/database/dbauthz/dbauthz.go‎

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ var (
417417
rbac.ResourceProvisionerJobs.Type: {policy.ActionRead,policy.ActionUpdate,policy.ActionCreate},
418418
rbac.ResourceOauth2App.Type: {policy.ActionCreate,policy.ActionRead,policy.ActionUpdate,policy.ActionDelete},
419419
rbac.ResourceOauth2AppSecret.Type: {policy.ActionCreate,policy.ActionRead,policy.ActionUpdate,policy.ActionDelete},
420+
rbac.ResourceOauth2AppCodeToken.Type: {policy.ActionCreate,policy.ActionRead,policy.ActionUpdate,policy.ActionDelete},
420421
}),
421422
Org:map[string][]rbac.Permission{},
422423
User: []rbac.Permission{},
@@ -1346,6 +1347,14 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
13461347
returnq.db.CleanTailnetTunnels(ctx)
13471348
}
13481349

1350+
func (q*querier)ConsumeOAuth2ProviderAppCodeByPrefix(ctx context.Context,secretPrefix []byte) (database.OAuth2ProviderAppCode,error) {
1351+
returnupdateWithReturn(q.log,q.auth,q.db.GetOAuth2ProviderAppCodeByPrefix,q.db.ConsumeOAuth2ProviderAppCodeByPrefix)(ctx,secretPrefix)
1352+
}
1353+
1354+
func (q*querier)ConsumeOAuth2ProviderDeviceCodeByPrefix(ctx context.Context,deviceCodePrefixstring) (database.OAuth2ProviderDeviceCode,error) {
1355+
returnupdateWithReturn(q.log,q.auth,q.db.GetOAuth2ProviderDeviceCodeByPrefix,q.db.ConsumeOAuth2ProviderDeviceCodeByPrefix)(ctx,deviceCodePrefix)
1356+
}
1357+
13491358
func (q*querier)CountAuditLogs(ctx context.Context,arg database.CountAuditLogsParams) (int64,error) {
13501359
// Shortcut if the user is an owner. The SQL filter is noticeable,
13511360
// and this is an easy win for owners. Which is the common case.
@@ -1560,27 +1569,30 @@ func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Contex
15601569
returnq.db.DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx,arg)
15611570
}
15621571

1563-
func (q*querier)DeleteOldAuditLogConnectionEvents(ctx context.Context,threshold database.DeleteOldAuditLogConnectionEventsParams)error {
1564-
// `ResourceSystem` is deprecated, but it doesn't make sense to add
1565-
// `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1566-
// only time we'll be deleting from the audit log.
1567-
iferr:=q.authorizeContext(ctx,policy.ActionDelete,rbac.ResourceSystem);err!=nil {
1568-
returnerr
1569-
}
1570-
returnq.db.DeleteOldAuditLogConnectionEvents(ctx,threshold)
1571-
}
1572-
15731572
func (q*querier)DeleteOAuth2ProviderDeviceCodeByID(ctx context.Context,id uuid.UUID)error {
15741573
// Fetch the device code first to check authorization
15751574
deviceCode,err:=q.db.GetOAuth2ProviderDeviceCodeByID(ctx,id)
15761575
iferr!=nil {
1577-
returnerr
1576+
returnxerrors.Errorf("get oauth2 provider device code: %w",err)
15781577
}
15791578
iferr:=q.authorizeContext(ctx,policy.ActionDelete,deviceCode);err!=nil {
1580-
returnerr
1579+
returnxerrors.Errorf("authorize oauth2 provider device code deletion: %w",err)
15811580
}
15821581

1583-
returnq.db.DeleteOAuth2ProviderDeviceCodeByID(ctx,id)
1582+
iferr:=q.db.DeleteOAuth2ProviderDeviceCodeByID(ctx,id);err!=nil {
1583+
returnxerrors.Errorf("delete oauth2 provider device code: %w",err)
1584+
}
1585+
returnnil
1586+
}
1587+
1588+
func (q*querier)DeleteOldAuditLogConnectionEvents(ctx context.Context,threshold database.DeleteOldAuditLogConnectionEventsParams)error {
1589+
// `ResourceSystem` is deprecated, but it doesn't make sense to add
1590+
// `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1591+
// only time we'll be deleting from the audit log.
1592+
iferr:=q.authorizeContext(ctx,policy.ActionDelete,rbac.ResourceSystem);err!=nil {
1593+
returnerr
1594+
}
1595+
returnq.db.DeleteOldAuditLogConnectionEvents(ctx,threshold)
15841596
}
15851597

15861598
func (q*querier)DeleteOldNotificationMessages(ctx context.Context)error {
@@ -2367,8 +2379,8 @@ func (q *querier) GetOAuth2ProviderDeviceCodeByUserCode(ctx context.Context, use
23672379
}
23682380

23692381
func (q*querier)GetOAuth2ProviderDeviceCodesByClientID(ctx context.Context,clientID uuid.UUID) ([]database.OAuth2ProviderDeviceCode,error) {
2370-
// This requires access to readtheOAuth2 app
2371-
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceOauth2App);err!=nil {
2382+
// This requires access to read OAuth2 app code tokens
2383+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceOauth2AppCodeToken);err!=nil {
23722384
return []database.OAuth2ProviderDeviceCode{},err
23732385
}
23742386
returnq.db.GetOAuth2ProviderDeviceCodesByClientID(ctx,clientID)
@@ -3810,8 +3822,8 @@ func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database
38103822
}
38113823

38123824
func (q*querier)InsertOAuth2ProviderDeviceCode(ctx context.Context,arg database.InsertOAuth2ProviderDeviceCodeParams) (database.OAuth2ProviderDeviceCode,error) {
3813-
// Creating device codes requires OAuth2 app access
3814-
iferr:=q.authorizeContext(ctx,policy.ActionCreate,rbac.ResourceOauth2App);err!=nil {
3825+
// Creating device codes requires OAuth2 appcode token creationaccess
3826+
iferr:=q.authorizeContext(ctx,policy.ActionCreate,rbac.ResourceOauth2AppCodeToken);err!=nil {
38153827
return database.OAuth2ProviderDeviceCode{},err
38163828
}
38173829
returnq.db.InsertOAuth2ProviderDeviceCode(ctx,arg)
@@ -4490,13 +4502,10 @@ func (q *querier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg dat
44904502
}
44914503

44924504
func (q*querier)UpdateOAuth2ProviderDeviceCodeAuthorization(ctx context.Context,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams) (database.OAuth2ProviderDeviceCode,error) {
4493-
// Verify the user is authenticated for device code authorization
4494-
_,ok:=ActorFromContext(ctx)
4495-
if!ok {
4496-
return database.OAuth2ProviderDeviceCode{},ErrNoActor
4505+
fetch:=func(ctx context.Context,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams) (database.OAuth2ProviderDeviceCode,error) {
4506+
returnq.db.GetOAuth2ProviderDeviceCodeByID(ctx,arg.ID)
44974507
}
4498-
4499-
returnq.db.UpdateOAuth2ProviderDeviceCodeAuthorization(ctx,arg)
4508+
returnupdateWithReturn(q.log,q.auth,fetch,q.db.UpdateOAuth2ProviderDeviceCodeAuthorization)(ctx,arg)
45004509
}
45014510

45024511
func (q*querier)UpdateOrganization(ctx context.Context,arg database.UpdateOrganizationParams) (database.Organization,error) {

‎coderd/database/dbauthz/dbauthz_test.go‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5553,6 +5553,19 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() {
55535553
UserID:user.ID,
55545554
}).Asserts(rbac.ResourceOauth2AppCodeToken.WithOwner(user.ID.String()),policy.ActionDelete)
55555555
}))
5556+
s.Run("ConsumeOAuth2ProviderAppCodeByPrefix",s.Subtest(func(db database.Store,check*expects) {
5557+
user:=dbgen.User(s.T(),db, database.User{})
5558+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5559+
// Use unique prefix to avoid test isolation issues
5560+
uniquePrefix:=fmt.Sprintf("prefix-%s-%d",s.T().Name(),time.Now().UnixNano())
5561+
code:=dbgen.OAuth2ProviderAppCode(s.T(),db, database.OAuth2ProviderAppCode{
5562+
SecretPrefix: []byte(uniquePrefix),
5563+
UserID:user.ID,
5564+
AppID:app.ID,
5565+
ExpiresAt:time.Now().Add(24*time.Hour),// Extended expiry for test stability
5566+
})
5567+
check.Args(code.SecretPrefix).Asserts(code,policy.ActionUpdate).Returns(code)
5568+
}))
55565569
}
55575570

55585571
func (s*MethodTestSuite)TestOAuth2ProviderAppTokens() {
@@ -5628,6 +5641,115 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
56285641
}))
56295642
}
56305643

5644+
func (s*MethodTestSuite)TestOAuth2ProviderDeviceCodes() {
5645+
s.Run("InsertOAuth2ProviderDeviceCode",s.Subtest(func(db database.Store,check*expects) {
5646+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5647+
check.Args(database.InsertOAuth2ProviderDeviceCodeParams{
5648+
ClientID:app.ID,
5649+
DeviceCodePrefix:"testpref",
5650+
DeviceCodeHash: []byte("hash"),
5651+
UserCode:"TEST1234",
5652+
VerificationUri:"http://example.com/device",
5653+
}).Asserts(rbac.ResourceOauth2AppCodeToken,policy.ActionCreate)
5654+
}))
5655+
s.Run("GetOAuth2ProviderDeviceCodeByID",s.Subtest(func(db database.Store,check*expects) {
5656+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5657+
deviceCode,err:=db.InsertOAuth2ProviderDeviceCode(context.Background(), database.InsertOAuth2ProviderDeviceCodeParams{
5658+
ClientID:app.ID,
5659+
DeviceCodePrefix:"testpref",
5660+
UserCode:"TEST1234",
5661+
VerificationUri:"http://example.com/device",
5662+
})
5663+
require.NoError(s.T(),err)
5664+
check.Args(deviceCode.ID).Asserts(deviceCode,policy.ActionRead).Returns(deviceCode)
5665+
}))
5666+
s.Run("GetOAuth2ProviderDeviceCodeByPrefix",s.Subtest(func(db database.Store,check*expects) {
5667+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5668+
deviceCode,err:=db.InsertOAuth2ProviderDeviceCode(context.Background(), database.InsertOAuth2ProviderDeviceCodeParams{
5669+
ClientID:app.ID,
5670+
DeviceCodePrefix:"testpref",
5671+
UserCode:"TEST1234",
5672+
VerificationUri:"http://example.com/device",
5673+
})
5674+
require.NoError(s.T(),err)
5675+
check.Args(deviceCode.DeviceCodePrefix).Asserts(deviceCode,policy.ActionRead).Returns(deviceCode)
5676+
}))
5677+
s.Run("GetOAuth2ProviderDeviceCodeByUserCode",s.Subtest(func(db database.Store,check*expects) {
5678+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5679+
deviceCode,err:=db.InsertOAuth2ProviderDeviceCode(context.Background(), database.InsertOAuth2ProviderDeviceCodeParams{
5680+
ClientID:app.ID,
5681+
DeviceCodePrefix:"testpref",
5682+
UserCode:"TEST1234",
5683+
VerificationUri:"http://example.com/device",
5684+
})
5685+
require.NoError(s.T(),err)
5686+
check.Args(deviceCode.UserCode).Asserts(deviceCode,policy.ActionRead).Returns(deviceCode)
5687+
}))
5688+
s.Run("GetOAuth2ProviderDeviceCodesByClientID",s.Subtest(func(db database.Store,check*expects) {
5689+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5690+
deviceCode,err:=db.InsertOAuth2ProviderDeviceCode(context.Background(), database.InsertOAuth2ProviderDeviceCodeParams{
5691+
ClientID:app.ID,
5692+
DeviceCodePrefix:"testpref",
5693+
UserCode:"TEST1234",
5694+
VerificationUri:"http://example.com/device",
5695+
})
5696+
require.NoError(s.T(),err)
5697+
check.Args(app.ID).Asserts(rbac.ResourceOauth2AppCodeToken,policy.ActionRead).Returns([]database.OAuth2ProviderDeviceCode{deviceCode})
5698+
}))
5699+
s.Run("ConsumeOAuth2ProviderDeviceCodeByPrefix",s.Subtest(func(db database.Store,check*expects) {
5700+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5701+
user:=dbgen.User(s.T(),db, database.User{})
5702+
// Use unique identifiers to avoid test isolation issues
5703+
// Device code prefix must be exactly 8 characters
5704+
uniquePrefix:=fmt.Sprintf("t%07d",time.Now().UnixNano()%10000000)
5705+
uniqueUserCode:=fmt.Sprintf("USER%04d",time.Now().UnixNano()%10000)
5706+
// Create device code using dbgen (now available!)
5707+
deviceCode:=dbgen.OAuth2ProviderDeviceCode(s.T(),db, database.OAuth2ProviderDeviceCode{
5708+
DeviceCodePrefix:uniquePrefix,
5709+
UserCode:uniqueUserCode,
5710+
ClientID:app.ID,
5711+
ExpiresAt:time.Now().Add(24*time.Hour),// Extended expiry for test stability
5712+
})
5713+
// Authorize the device code so it can be consumed
5714+
deviceCode,err:=db.UpdateOAuth2ProviderDeviceCodeAuthorization(s.T().Context(), database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams{
5715+
ID:deviceCode.ID,
5716+
UserID: uuid.NullUUID{UUID:user.ID,Valid:true},
5717+
Status:database.OAuth2DeviceStatusAuthorized,
5718+
})
5719+
require.NoError(s.T(),err)
5720+
require.Equal(s.T(),database.OAuth2DeviceStatusAuthorized,deviceCode.Status)
5721+
check.Args(uniquePrefix).Asserts(deviceCode,policy.ActionUpdate).Returns(deviceCode)
5722+
}))
5723+
s.Run("UpdateOAuth2ProviderDeviceCodeAuthorization",s.Subtest(func(db database.Store,check*expects) {
5724+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5725+
user:=dbgen.User(s.T(),db, database.User{})
5726+
// Create device code using dbgen
5727+
deviceCode:=dbgen.OAuth2ProviderDeviceCode(s.T(),db, database.OAuth2ProviderDeviceCode{
5728+
ClientID:app.ID,
5729+
})
5730+
require.Equal(s.T(),database.OAuth2DeviceStatusPending,deviceCode.Status)
5731+
check.Args(database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams{
5732+
ID:deviceCode.ID,
5733+
UserID: uuid.NullUUID{UUID:user.ID,Valid:true},
5734+
Status:database.OAuth2DeviceStatusAuthorized,
5735+
}).Asserts(deviceCode,policy.ActionUpdate)
5736+
}))
5737+
s.Run("DeleteOAuth2ProviderDeviceCodeByID",s.Subtest(func(db database.Store,check*expects) {
5738+
app:=dbgen.OAuth2ProviderApp(s.T(),db, database.OAuth2ProviderApp{})
5739+
deviceCode,err:=db.InsertOAuth2ProviderDeviceCode(context.Background(), database.InsertOAuth2ProviderDeviceCodeParams{
5740+
ClientID:app.ID,
5741+
DeviceCodePrefix:"testpref",
5742+
UserCode:"TEST1234",
5743+
VerificationUri:"http://example.com/device",
5744+
})
5745+
require.NoError(s.T(),err)
5746+
check.Args(deviceCode.ID).Asserts(deviceCode,policy.ActionDelete)
5747+
}))
5748+
s.Run("DeleteExpiredOAuth2ProviderDeviceCodes",s.Subtest(func(db database.Store,check*expects) {
5749+
check.Args().Asserts(rbac.ResourceSystem,policy.ActionDelete)
5750+
}))
5751+
}
5752+
56315753
func (s*MethodTestSuite)TestResourcesMonitor() {
56325754
createAgent:=func(t*testing.T,db database.Store) (database.WorkspaceAgent, database.WorkspaceTable) {
56335755
t.Helper()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp