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: make OAuth2 provider not enterprise-only#12732

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

Merged
code-asher merged 1 commit intomainfromasher/oauth2-not-enterprise
Mar 25, 2024
Merged
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
56 changes: 56 additions & 0 deletionscoderd/coderd.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -689,6 +689,34 @@ func New(options *Options) *API {
})
}

// OAuth2 linking routes do not make sense under the /api/v2 path. These are
// for an external application to use Coder as an OAuth2 provider, not for
// logging into Coder with an external OAuth2 provider.
r.Route("/oauth2", func(r chi.Router) {
r.Use(
api.oAuth2ProviderMiddleware,
// Fetch the app as system because in the /tokens route there will be no
// authenticated user.
httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderApp(options.Database)),
)
r.Route("/authorize", func(r chi.Router) {
r.Use(apiKeyMiddlewareRedirect)
r.Get("/", api.getOAuth2ProviderAppAuthorize())
})
r.Route("/tokens", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(apiKeyMiddleware)
// DELETE on /tokens is not part of the OAuth2 spec. It is our own
// route used to revoke permissions from an application. It is here for
// parity with POST on /tokens.
r.Delete("/", api.deleteOAuth2ProviderAppTokens())
})
// The POST /tokens endpoint will be called from an unauthorized client so
// we cannot require an API key.
r.Post("/", api.postOAuth2ProviderAppToken())
})
})

r.Route("/api/v2", func(r chi.Router) {
api.APIHandler = r

Expand DownExpand Up@@ -1098,6 +1126,34 @@ func New(options *Options) *API {
}
r.Method("GET", "/expvar", expvar.Handler()) // contains DERP metrics as well as cmdline and memstats
})
// Manage OAuth2 applications that can use Coder as an OAuth2 provider.
r.Route("/oauth2-provider", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
api.oAuth2ProviderMiddleware,
)
r.Route("/apps", func(r chi.Router) {
r.Get("/", api.oAuth2ProviderApps)
r.Post("/", api.postOAuth2ProviderApp)

r.Route("/{app}", func(r chi.Router) {
r.Use(httpmw.ExtractOAuth2ProviderApp(options.Database))
r.Get("/", api.oAuth2ProviderApp)
r.Put("/", api.putOAuth2ProviderApp)
r.Delete("/", api.deleteOAuth2ProviderApp)

r.Route("/secrets", func(r chi.Router) {
r.Get("/", api.oAuth2ProviderAppSecrets)
r.Post("/", api.postOAuth2ProviderAppSecret)

r.Route("/{secretID}", func(r chi.Router) {
r.Use(httpmw.ExtractOAuth2ProviderAppSecret(options.Database))
r.Delete("/", api.deleteOAuth2ProviderAppSecret)
})
})
})
})
})
})

if options.SwaggerEndpoint {
Expand Down
25 changes: 7 additions & 18 deletionsenterprise/coderd/oauth2.go → coderd/oauth2.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,11 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/identityprovider"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/identityprovider"
)

func (api*API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
func (*API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !buildinfo.IsDev() {
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
Expand All@@ -26,17 +26,6 @@ func (api *API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
return
}

api.entitlementsMu.RLock()
entitled := api.entitlements.Features[codersdk.FeatureOAuth2Provider].Entitlement != codersdk.EntitlementNotEntitled
api.entitlementsMu.RUnlock()

if !entitled {
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
Message: "OAuth2 provider is an Enterprise feature. Contact sales!",
})
return
}

next.ServeHTTP(rw, r)
})
}
Expand DownExpand Up@@ -111,7 +100,7 @@ func (api *API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
func (api *API) postOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
auditor = api.AGPL.Auditor.Load()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Expand DownExpand Up@@ -157,7 +146,7 @@ func (api *API) putOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
app = httpmw.OAuth2ProviderApp(r)
auditor = api.AGPL.Auditor.Load()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Expand DownExpand Up@@ -200,7 +189,7 @@ func (api *API) deleteOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request)
var (
ctx = r.Context()
app = httpmw.OAuth2ProviderApp(r)
auditor = api.AGPL.Auditor.Load()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderApp](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Expand DownExpand Up@@ -263,7 +252,7 @@ func (api *API) postOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Requ
var (
ctx = r.Context()
app = httpmw.OAuth2ProviderApp(r)
auditor = api.AGPL.Auditor.Load()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderAppSecret](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Expand DownExpand Up@@ -317,7 +306,7 @@ func (api *API) deleteOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Re
var (
ctx = r.Context()
secret = httpmw.OAuth2ProviderAppSecret(r)
auditor = api.AGPL.Auditor.Load()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.OAuth2ProviderAppSecret](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Expand Down
81 changes: 28 additions & 53 deletionsenterprise/coderd/oauth2_test.go → coderd/oauth2_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,12 +19,10 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/identityprovider"
"github.com/coder/coder/v2/coderd/userpassword"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/identityprovider"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)

Expand All@@ -34,11 +32,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
t.Run("Validation", func(t *testing.T) {
t.Parallel()

client, _ := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)

topCtx := testutil.Context(t, testutil.WaitLong)

Expand DownExpand Up@@ -178,11 +173,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
t.Run("DeleteNonExisting", func(t *testing.T) {
t.Parallel()

client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
another, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)

ctx := testutil.Context(t, testutil.WaitLong)
Expand All@@ -194,11 +186,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
t.Run("OK", func(t *testing.T) {
t.Parallel()

client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
another, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)

ctx := testutil.Context(t, testutil.WaitLong)
Expand DownExpand Up@@ -269,11 +258,8 @@ func TestOAuth2ProviderApps(t *testing.T) {

t.Run("ByUser", func(t *testing.T) {
t.Parallel()
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
another, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
ctx := testutil.Context(t, testutil.WaitLong)
_ = generateApps(ctx, t, client, "by-user")
Expand All@@ -288,11 +274,8 @@ func TestOAuth2ProviderApps(t *testing.T) {
func TestOAuth2ProviderAppSecrets(t *testing.T) {
t.Parallel()

client, _ := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)

topCtx := testutil.Context(t, testutil.WaitLong)

Expand DownExpand Up@@ -383,17 +366,11 @@ func TestOAuth2ProviderTokenExchange(t *testing.T) {
t.Parallel()

db, pubsub := dbtestutil.NewDB(t)
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
},
ownerClient := coderdtest.New(t, &coderdtest.Options{
Database: db,
Pubsub: pubsub,
})
owner := coderdtest.CreateFirstUser(t, ownerClient)
topCtx := testutil.Context(t, testutil.WaitLong)
apps := generateApps(topCtx, t, ownerClient, "token-exchange")

Expand DownExpand Up@@ -764,17 +741,11 @@ func TestOAuth2ProviderTokenRefresh(t *testing.T) {
topCtx := testutil.Context(t, testutil.WaitLong)

db, pubsub := dbtestutil.NewDB(t)
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
},
ownerClient := coderdtest.New(t, &coderdtest.Options{
Database: db,
Pubsub: pubsub,
})
owner := coderdtest.CreateFirstUser(t, ownerClient)
apps := generateApps(topCtx, t, ownerClient, "token-refresh")

//nolint:gocritic // OAauth2 app management requires owner permission.
Expand DownExpand Up@@ -935,11 +906,8 @@ type exchangeSetup struct {
func TestOAuth2ProviderRevoke(t *testing.T) {
t.Parallel()

client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureOAuth2Provider: 1,
},
}})
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)

tests := []struct {
name string
Expand DownExpand Up@@ -1138,3 +1106,10 @@ func authorizationFlow(ctx context.Context, client *codersdk.Client, cfg *oauth2
},
)
}

func must[T any](value T, err error) T {
if err != nil {
panic(err)
}
return value
}
4 changes: 0 additions & 4 deletionscodersdk/deployment.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,7 +53,6 @@ const (
FeatureExternalTokenEncryption FeatureName = "external_token_encryption"
FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions"
FeatureAccessControl FeatureName = "access_control"
FeatureOAuth2Provider FeatureName = "oauth2_provider"
FeatureControlSharedPorts FeatureName = "control_shared_ports"
)

Expand All@@ -74,7 +73,6 @@ var FeatureNames = []FeatureName{
FeatureExternalTokenEncryption,
FeatureWorkspaceBatchActions,
FeatureAccessControl,
FeatureOAuth2Provider,
FeatureControlSharedPorts,
}

Expand All@@ -85,8 +83,6 @@ func (n FeatureName) Humanize() string {
return "Template RBAC"
case FeatureSCIM:
return "SCIM"
case FeatureOAuth2Provider:
return "OAuth Provider"
default:
return strings.Title(strings.ReplaceAll(string(n), "_", " "))
}
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp