Skip to content

Commit c7d69e2

Browse files
committed
Add database tables for OAuth2 applications
These are applications that will be able to use OAuth2 to get an API key from Coder.
1 parent eb81fcf commit c7d69e2

19 files changed

+1084
-0
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,20 @@ func (q *querier) DeleteLicense(ctx context.Context, id int32) (int32, error) {
800800
return id, nil
801801
}
802802

803+
func (q *querier) DeleteOAuth2AppByID(ctx context.Context, id uuid.UUID) error {
804+
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2App); err != nil {
805+
return err
806+
}
807+
return q.db.DeleteOAuth2AppByID(ctx, id)
808+
}
809+
810+
func (q *querier) DeleteOAuth2AppSecretByID(ctx context.Context, id uuid.UUID) error {
811+
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2AppSecret); err != nil {
812+
return err
813+
}
814+
return q.db.DeleteOAuth2AppSecretByID(ctx, id)
815+
}
816+
803817
func (q *querier) DeleteOldProvisionerDaemons(ctx context.Context) error {
804818
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil {
805819
return err
@@ -1126,6 +1140,34 @@ func (q *querier) GetLogoURL(ctx context.Context) (string, error) {
11261140
return q.db.GetLogoURL(ctx)
11271141
}
11281142

1143+
func (q *querier) GetOAuth2AppByID(ctx context.Context, id uuid.UUID) (database.OAuth2App, error) {
1144+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2App); err != nil {
1145+
return database.OAuth2App{}, err
1146+
}
1147+
return q.db.GetOAuth2AppByID(ctx, id)
1148+
}
1149+
1150+
func (q *querier) GetOAuth2AppSecretByID(ctx context.Context, id uuid.UUID) (database.OAuth2AppSecret, error) {
1151+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2AppSecret); err != nil {
1152+
return database.OAuth2AppSecret{}, err
1153+
}
1154+
return q.db.GetOAuth2AppSecretByID(ctx, id)
1155+
}
1156+
1157+
func (q *querier) GetOAuth2AppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]database.OAuth2AppSecret, error) {
1158+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2AppSecret); err != nil {
1159+
return []database.OAuth2AppSecret{}, err
1160+
}
1161+
return q.db.GetOAuth2AppSecretsByAppID(ctx, appID)
1162+
}
1163+
1164+
func (q *querier) GetOAuth2Apps(ctx context.Context) ([]database.OAuth2App, error) {
1165+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2App); err != nil {
1166+
return []database.OAuth2App{}, err
1167+
}
1168+
return q.db.GetOAuth2Apps(ctx)
1169+
}
1170+
11291171
func (q *querier) GetOAuthSigningKey(ctx context.Context) (string, error) {
11301172
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
11311173
return "", err
@@ -2140,6 +2182,20 @@ func (q *querier) InsertMissingGroups(ctx context.Context, arg database.InsertMi
21402182
return q.db.InsertMissingGroups(ctx, arg)
21412183
}
21422184

2185+
func (q *querier) InsertOAuth2App(ctx context.Context, arg database.InsertOAuth2AppParams) (database.OAuth2App, error) {
2186+
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2App); err != nil {
2187+
return database.OAuth2App{}, err
2188+
}
2189+
return q.db.InsertOAuth2App(ctx, arg)
2190+
}
2191+
2192+
func (q *querier) InsertOAuth2AppSecret(ctx context.Context, arg database.InsertOAuth2AppSecretParams) (database.OAuth2AppSecret, error) {
2193+
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2AppSecret); err != nil {
2194+
return database.OAuth2AppSecret{}, err
2195+
}
2196+
return q.db.InsertOAuth2AppSecret(ctx, arg)
2197+
}
2198+
21432199
func (q *querier) InsertOrganization(ctx context.Context, arg database.InsertOrganizationParams) (database.Organization, error) {
21442200
return insert(q.log, q.auth, rbac.ResourceOrganization, q.db.InsertOrganization)(ctx, arg)
21452201
}
@@ -2493,6 +2549,20 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
24932549
return q.db.UpdateMemberRoles(ctx, arg)
24942550
}
24952551

2552+
func (q *querier) UpdateOAuth2AppByID(ctx context.Context, arg database.UpdateOAuth2AppByIDParams) (database.OAuth2App, error) {
2553+
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceOAuth2App); err != nil {
2554+
return database.OAuth2App{}, err
2555+
}
2556+
return q.db.UpdateOAuth2AppByID(ctx, arg)
2557+
}
2558+
2559+
func (q *querier) UpdateOAuth2AppSecretByID(ctx context.Context, arg database.UpdateOAuth2AppSecretByIDParams) (database.OAuth2AppSecret, error) {
2560+
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceOAuth2AppSecret); err != nil {
2561+
return database.OAuth2AppSecret{}, err
2562+
}
2563+
return q.db.UpdateOAuth2AppSecretByID(ctx, arg)
2564+
}
2565+
24962566
// TODO: We need to create a ProvisionerJob resource type
24972567
func (q *querier) UpdateProvisionerJobByID(ctx context.Context, arg database.UpdateProvisionerJobByIDParams) error {
24982568
// if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,3 +1711,86 @@ func (s *MethodTestSuite) TestSystemFunctions() {
17111711
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
17121712
}))
17131713
}
1714+
1715+
func (s *MethodTestSuite) TestOAuth2Apps() {
1716+
s.Run("GetOAuth2Apps", s.Subtest(func(db database.Store, check *expects) {
1717+
apps := []database.OAuth2App{
1718+
dbgen.OAuth2App(s.T(), db, database.OAuth2App{Name: "first"}),
1719+
dbgen.OAuth2App(s.T(), db, database.OAuth2App{Name: "last"}),
1720+
}
1721+
check.Args().Asserts(rbac.ResourceOAuth2App, rbac.ActionRead).Returns(apps)
1722+
}))
1723+
s.Run("GetOAuth2AppByID", s.Subtest(func(db database.Store, check *expects) {
1724+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1725+
check.Args(app.ID).Asserts(rbac.ResourceOAuth2App, rbac.ActionRead).Returns(app)
1726+
}))
1727+
s.Run("InsertOAuth2App", s.Subtest(func(db database.Store, check *expects) {
1728+
check.Args(database.InsertOAuth2AppParams{}).Asserts(rbac.ResourceOAuth2App, rbac.ActionCreate)
1729+
}))
1730+
s.Run("UpdateOAuth2AppByID", s.Subtest(func(db database.Store, check *expects) {
1731+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1732+
app.Name = "my-new-name"
1733+
app.UpdatedAt = time.Now()
1734+
check.Args(database.UpdateOAuth2AppByIDParams{
1735+
ID: app.ID,
1736+
Name: app.Name,
1737+
CallbackURL: app.CallbackURL,
1738+
UpdatedAt: app.UpdatedAt,
1739+
}).Asserts(rbac.ResourceOAuth2App, rbac.ActionUpdate).Returns(app)
1740+
}))
1741+
s.Run("DeleteOAuth2AppByID", s.Subtest(func(db database.Store, check *expects) {
1742+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1743+
check.Args(app.ID).Asserts(rbac.ResourceOAuth2App, rbac.ActionDelete)
1744+
}))
1745+
}
1746+
1747+
func (s *MethodTestSuite) TestOAuth2AppSecrets() {
1748+
s.Run("GetOAuth2AppSecretsByAppID", s.Subtest(func(db database.Store, check *expects) {
1749+
app1 := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1750+
app2 := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1751+
secrets := []database.OAuth2AppSecret{
1752+
dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1753+
AppID: app1.ID,
1754+
CreatedAt: time.Now().Add(-time.Hour), // For ordering.
1755+
}),
1756+
dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1757+
AppID: app1.ID,
1758+
}),
1759+
}
1760+
_ = dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1761+
AppID: app2.ID,
1762+
})
1763+
check.Args(app1.ID).Asserts(rbac.ResourceOAuth2AppSecret, rbac.ActionRead).Returns(secrets)
1764+
}))
1765+
s.Run("GetOAuth2AppSecretByID", s.Subtest(func(db database.Store, check *expects) {
1766+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1767+
secret := dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1768+
AppID: app.ID,
1769+
})
1770+
check.Args(secret.ID).Asserts(rbac.ResourceOAuth2AppSecret, rbac.ActionRead).Returns(secret)
1771+
}))
1772+
s.Run("InsertOAuth2AppSecret", s.Subtest(func(db database.Store, check *expects) {
1773+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1774+
check.Args(database.InsertOAuth2AppSecretParams{
1775+
AppID: app.ID,
1776+
}).Asserts(rbac.ResourceOAuth2AppSecret, rbac.ActionCreate)
1777+
}))
1778+
s.Run("UpdateOAuth2AppSecretByID", s.Subtest(func(db database.Store, check *expects) {
1779+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1780+
secret := dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1781+
AppID: app.ID,
1782+
})
1783+
secret.LastUsedAt = sql.NullTime{Time: time.Now(), Valid: true}
1784+
check.Args(database.UpdateOAuth2AppSecretByIDParams{
1785+
ID: secret.ID,
1786+
LastUsedAt: secret.LastUsedAt,
1787+
}).Asserts(rbac.ResourceOAuth2AppSecret, rbac.ActionUpdate).Returns(secret)
1788+
}))
1789+
s.Run("DeleteOAuth2AppSecretByID", s.Subtest(func(db database.Store, check *expects) {
1790+
app := dbgen.OAuth2App(s.T(), db, database.OAuth2App{})
1791+
secret := dbgen.OAuth2AppSecret(s.T(), db, database.OAuth2AppSecret{
1792+
AppID: app.ID,
1793+
})
1794+
check.Args(secret.ID).Asserts(rbac.ResourceOAuth2AppSecret, rbac.ActionDelete)
1795+
}))
1796+
}

coderd/database/dbgen/dbgen.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,31 @@ func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.Workspace
676676
return scheme
677677
}
678678

679+
func OAuth2App(t testing.TB, db database.Store, seed database.OAuth2App) database.OAuth2App {
680+
app, err := db.InsertOAuth2App(genCtx, database.InsertOAuth2AppParams{
681+
ID: takeFirst(seed.ID, uuid.New()),
682+
Name: takeFirst(seed.Name, namesgenerator.GetRandomName(1)),
683+
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
684+
UpdatedAt: takeFirst(seed.UpdatedAt, dbtime.Now()),
685+
Icon: takeFirst(seed.Icon, ""),
686+
CallbackURL: takeFirst(seed.CallbackURL, "http://localhost"),
687+
})
688+
require.NoError(t, err, "insert oauth2 app")
689+
return app
690+
}
691+
692+
func OAuth2AppSecret(t testing.TB, db database.Store, seed database.OAuth2AppSecret) database.OAuth2AppSecret {
693+
app, err := db.InsertOAuth2AppSecret(genCtx, database.InsertOAuth2AppSecretParams{
694+
ID: takeFirst(seed.ID, uuid.New()),
695+
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
696+
HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")),
697+
DisplaySecret: takeFirst(seed.DisplaySecret, "secret"),
698+
AppID: takeFirst(seed.AppID, uuid.New()),
699+
})
700+
require.NoError(t, err, "insert oauth2 app secret")
701+
return app
702+
}
703+
679704
func must[V any](v V, err error) V {
680705
if err != nil {
681706
panic(err)

0 commit comments

Comments
 (0)