Ctrl K
ComunidadGuías…

codersdk/oauth2_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package codersdk_test

import (
	"net/url"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/codersdk"
)

// TestOAuth2ClientRegistrationRequest_DetermineClientType verifies that the
// client type is derived from the requested token_endpoint_auth_method
// (RFC 7591 §2, OAuth 2.1 §2.1), not hardcoded to "confidential".
func TestOAuth2ClientRegistrationRequest_DetermineClientType(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		authMethod codersdk.OAuth2TokenEndpointAuthMethod
		// applyDefaults runs ApplyDefaults() before DetermineClientType(),
		// matching the real request path where an omitted auth method is
		// defaulted to "client_secret_basic" before this check ever runs.
		applyDefaults bool
		// wantAuthMethodAfterDefaults pins what ApplyDefaults() does to
		// authMethod, so a case that runs applyDefaults also verifies
		// ApplyDefaults left (or changed) the field as expected before
		// DetermineClientType() reads it. Only checked when applyDefaults
		// is true.
		wantAuthMethodAfterDefaults codersdk.OAuth2TokenEndpointAuthMethod
		expectedType                string
	}{
		{
			name:         "NoneIsPublic",
			authMethod:   codersdk.OAuth2TokenEndpointAuthMethodNone,
			expectedType: "public",
		},
		{
			name:         "ClientSecretBasicIsConfidential",
			authMethod:   codersdk.OAuth2TokenEndpointAuthMethodClientSecretBasic,
			expectedType: "confidential",
		},
		{
			name:         "ClientSecretPostIsConfidential",
			authMethod:   codersdk.OAuth2TokenEndpointAuthMethodClientSecretPost,
			expectedType: "confidential",
		},
		{
			// ApplyDefaults only fills an empty auth method; it must not
			// touch an explicit "none". If it ever grew a rule that did,
			// the pre-defaults Validate() call and the post-defaults
			// storage call would disagree about this client's type.
			name:                        "NoneStaysPublicAfterApplyDefaults",
			authMethod:                  codersdk.OAuth2TokenEndpointAuthMethodNone,
			applyDefaults:               true,
			wantAuthMethodAfterDefaults: codersdk.OAuth2TokenEndpointAuthMethodNone,
			expectedType:                "public",
		},
		{
			// An omitted auth method must not be read as public. Without
			// ApplyDefaults the empty string also falls through to
			// confidential, so this is safe in either order, but the real
			// path always defaults first.
			name:                        "OmittedDefaultsToConfidentialAfterApplyDefaults",
			applyDefaults:               true,
			wantAuthMethodAfterDefaults: codersdk.OAuth2TokenEndpointAuthMethodClientSecretBasic,
			expectedType:                "confidential",
		},
		{
			name:         "OmittedIsConfidentialWithoutApplyDefaults",
			expectedType: "confidential",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			req := codersdk.OAuth2ClientRegistrationRequest{
				TokenEndpointAuthMethod: tt.authMethod,
			}
			if tt.applyDefaults {
				req = req.ApplyDefaults()
				require.Equal(t, tt.wantAuthMethodAfterDefaults, req.TokenEndpointAuthMethod)
			}
			require.Equal(t, tt.expectedType, string(req.DetermineClientType()))
		})
	}
}

func TestRedirectURIMatches(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		registered string
		presented  string
		want       bool
	}{
		{"ExactMatch", "https://app.example.com/callback", "https://app.example.com/callback", true},
		{"CustomSchemeExact", "cursor://anysphere.cursor-mcp/oauth/callback", "cursor://anysphere.cursor-mcp/oauth/callback", true},
		{"LoopbackIPv4PortDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/callback", true},
		{"LoopbackIPv6PortDiffers", "http://[::1]/callback", "http://[::1]:53219/callback", true},
		{"LocalhostPortDiffers", "http://localhost/callback", "http://localhost:53219/callback", true},
		{"RegisteredPortDoesNotPin", "http://localhost:9876/callback", "http://localhost:53219/callback", true},
		{"PresentedWithoutPort", "http://127.0.0.1:53219/callback", "http://127.0.0.1/callback", true},
		{"LoopbackPathDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/other", false},
		{"LoopbackSchemeDiffers", "http://127.0.0.1/callback", "https://127.0.0.1:53219/callback", false},
		{"LoopbackHostSubstitution", "http://127.0.0.1/callback", "http://localhost:53219/callback", false},
		{"LoopbackQueryDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/callback?next=x", false},
		{"LoopbackUserinfoDiffers", "http://127.0.0.1/callback", "http://user@127.0.0.1:53219/callback", false},
		{"LocalhostSubdomain", "http://app.localhost/callback", "http://app.localhost:53219/callback", false},
		{"OtherLoopbackIP", "http://127.0.0.2/callback", "http://127.0.0.2:53219/callback", false},
		{"HTTPSLoopbackPortDiffers", "https://127.0.0.1/callback", "https://127.0.0.1:53219/callback", false},
		{"NonLoopbackPortDiffers", "https://app.example.com/callback", "https://app.example.com:8443/callback", false},
		{"LoopbackPresentedAgainstNonLoopback", "https://app.example.com/callback", "http://127.0.0.1:53219/callback", false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			registered, err := url.Parse(tt.registered)
			require.NoError(t, err)
			presented, err := url.Parse(tt.presented)
			require.NoError(t, err)
			require.Equal(t, tt.want, codersdk.RedirectURIMatches(presented, registered))
		})
	}
}