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

Skip to content
Closed
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
8 changes: 7 additions & 1 deletion coderd/apidoc/docs.go

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

8 changes: 7 additions & 1 deletion coderd/apidoc/swagger.json

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

20 changes: 20 additions & 0 deletions coderd/database/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,23 @@ import (
// for use as a uuid.UUID. Both must agree; tests pin the value to the
// codersdk constant so the two cannot drift.
var PrebuildsSystemUserID = uuid.MustParse(codersdk.PrebuildsSystemUserID)

// Values stored in oauth2_provider_apps.client_type, as plain strings for
// comparison against the nullable column.
//
// Converted from the codersdk constants rather than redeclared, so the value
// registration writes and the value OAuth2ProviderApp.IsPublic reads back
// cannot disagree. That divergence would fail closed anyway (the app would read
// as confidential and demand a secret it was never issued), but it would fail
// visibly to a client rather than here.
//
// What this does not protect against is the two constants colliding on the same
// value, which would make IsPublic true for confidential apps. Nothing in the
// type system can catch that; the tests that pin these spellings to the wire
// values do, so do not delete them as redundant:
// TestOAuth2ClientRegistrationRequest_DetermineClientType and
// TestCreateDynamicClientRegistration_ClientType.
const (
OAuth2ProviderAppClientTypeConfidential = string(codersdk.OAuth2ClientTypeConfidential)
OAuth2ProviderAppClientTypePublic = string(codersdk.OAuth2ClientTypePublic)
)
2 changes: 1 addition & 1 deletion coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1733,7 +1733,7 @@ func OAuth2ProviderApp(t testing.TB, db database.Store, seed database.OAuth2Prov
Icon: takeFirst(seed.Icon, ""),
CallbackURL: takeFirst(seed.CallbackURL, "http://localhost"),
RedirectUris: takeFirstSlice(seed.RedirectUris, []string{}),
ClientType: takeFirst(seed.ClientType, "confidential"),
ClientType: takeFirst(seed.ClientType, database.OAuth2ProviderAppClientTypeConfidential),
DynamicallyRegistered: takeFirst(seed.DynamicallyRegistered, sql.NullBool{Bool: false, Valid: true}),
ClientIDIssuedAt: takeFirst(seed.ClientIDIssuedAt, sql.NullTime{}),
ClientSecretExpiresAt: takeFirst(seed.ClientSecretExpiresAt, sql.NullTime{}),
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/modelmethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,14 @@ func (OAuth2ProviderApp) RBACObject() rbac.Object {
return rbac.ResourceOauth2App
}

// IsPublic reports whether the app is a public (secretless, PKCE-only)
// OAuth2 client per RFC 7591 §2 / OAuth 2.1 §2.1, as opposed to confidential.
// An unset or unrecognized client type reads as confidential, so an app can
// never skip client authentication by accident.
func (a OAuth2ProviderApp) IsPublic() bool {
return a.ClientType == OAuth2ProviderAppClientTypePublic
}

func (a GetOAuth2ProviderAppsByUserIDRow) RBACObject() rbac.Object {
return a.OAuth2ProviderApp.RBACObject()
}
Expand Down
31 changes: 31 additions & 0 deletions coderd/database/modelmethods_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,37 @@ func TestWorkspaceACLDisabled(t *testing.T) {
})
}

// TestOAuth2ProviderAppIsPublic pins IsPublic's contract directly, since it is
// what decides whether the token endpoint validates a client secret at all.
// Only the exact string "public" may read as public: anything else, including
// an unset column or a differently-cased value, must read as confidential so
// that a garbled value cannot silently skip client authentication.
func TestOAuth2ProviderAppIsPublic(t *testing.T) {
t.Parallel()

tests := []struct {
clientType string
want bool
}{
{clientType: "public", want: true},
{clientType: "confidential", want: false},
{clientType: "", want: false},
{clientType: "Public", want: false},
{clientType: "PUBLIC", want: false},
{clientType: " public", want: false},
{clientType: "public ", want: false},
{clientType: "bogus", want: false},
}

for _, tt := range tests {
t.Run(tt.clientType, func(t *testing.T) {
t.Parallel()
app := OAuth2ProviderApp{ClientType: tt.clientType}
require.Equal(t, tt.want, app.IsPublic())
})
}
}

// Helpers
func requirePermission(t *testing.T, s rbac.Scope, resource string, action policy.Action) {
t.Helper()
Expand Down
3 changes: 2 additions & 1 deletion coderd/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ func (api *API) postOAuth2ProviderAppAuthorize() http.HandlerFunc {
// @Produce json
// @Tags Enterprise
// @Param client_id formData string false "Client ID, required if grant_type=authorization_code"
// @Param client_secret formData string false "Client secret, required if grant_type=authorization_code"
// @Param client_secret formData string false "Client secret, required if grant_type=authorization_code and the client is confidential. Public clients (token_endpoint_auth_method=none) send no secret."
// @Param code formData string false "Authorization code, required if grant_type=authorization_code"
// @Param code_verifier formData string false "PKCE code verifier, required if grant_type=authorization_code. 43-128 characters per RFC 7636. This is the only client authentication a public client has."
// @Param refresh_token formData string false "Refresh token, required if grant_type=refresh_token"
// @Param grant_type formData codersdk.OAuth2ProviderGrantType true "Grant type"
// @Success 200 {object} oauth2.Token
Expand Down
13 changes: 13 additions & 0 deletions coderd/oauth2_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,19 @@ func TestOAuth2PrivilegeEscalation(t *testing.T) {
ClientName: fmt.Sprintf("native-app-3-%d", time.Now().UnixNano()),
TokenEndpointAuthMethod: "none", // Required for public clients
},
{
// Bare custom schemes (no reverse-domain notation) are the
// schemes real native apps register with the OS, and PKCE,
// not the scheme's spelling, is what secures the redirect.
RedirectURIs: []string{"vscode://coder.authenticate"},
ClientName: fmt.Sprintf("native-app-vscode-%d", time.Now().UnixNano()),
TokenEndpointAuthMethod: "none",
},
{
RedirectURIs: []string{"jetbrains://coder-callback"},
ClientName: fmt.Sprintf("native-app-jetbrains-%d", time.Now().UnixNano()),
TokenEndpointAuthMethod: "none",
},
}

for i, req := range validCustomSchemeRequests {
Expand Down
Loading
Loading