Ctrl K
ComunidadGuías…

coderd/httpmw/cors_test.go

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

Archivo original ↗Preguntar a KiBot
package httpmw_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/coderd/httpmw"
	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
)

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

	regex, err := appurl.CompileHostnamePattern("*--apps.dev.coder.com")
	require.NoError(t, err)

	methods := []string{
		http.MethodOptions,
		http.MethodHead,
		http.MethodGet,
		http.MethodPost,
		http.MethodPut,
		http.MethodPatch,
		http.MethodDelete,
	}

	tests := []struct {
		name    string
		origin  string
		app     appurl.ApplicationURL
		allowed bool
	}{
		{
			name:   "Self",
			origin: "https://3000--agent--ws--user--apps.dev.coder.com",
			app: appurl.ApplicationURL{
				AppSlugOrPort: "3000",
				AgentName:     "agent",
				WorkspaceName: "ws",
				Username:      "user",
			},
			allowed: true,
		},
		{
			name:   "SameWorkspace",
			origin: "https://8000--agent--ws--user--apps.dev.coder.com",
			app: appurl.ApplicationURL{
				AppSlugOrPort: "3000",
				AgentName:     "agent",
				WorkspaceName: "ws",
				Username:      "user",
			},
			allowed: true,
		},
		{
			name:   "SameUser",
			origin: "https://8000--agent2--ws2--user--apps.dev.coder.com",
			app: appurl.ApplicationURL{
				AppSlugOrPort: "3000",
				AgentName:     "agent",
				WorkspaceName: "ws",
				Username:      "user",
			},
			allowed: true,
		},
		{
			name:   "DifferentOriginOwner",
			origin: "https://3000--agent--ws--user2--apps.dev.coder.com",
			app: appurl.ApplicationURL{
				AppSlugOrPort: "3000",
				AgentName:     "agent",
				WorkspaceName: "ws",
				Username:      "user",
			},
			allowed: false,
		},
		{
			name:   "DifferentHostOwner",
			origin: "https://3000--agent--ws--user--apps.dev.coder.com",
			app: appurl.ApplicationURL{
				AppSlugOrPort: "3000",
				AgentName:     "agent",
				WorkspaceName: "ws",
				Username:      "user2",
			},
			allowed: false,
		},
	}

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

			for _, method := range methods {
				r := httptest.NewRequest(method, "http://localhost", nil)
				r.Header.Set("Origin", test.origin)
				rw := httptest.NewRecorder()

				// Preflight requests need to know what method will be requested.
				if method == http.MethodOptions {
					r.Header.Set("Access-Control-Request-Method", method)
				}

				handler := httpmw.WorkspaceAppCors(regex, test.app)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
					rw.WriteHeader(http.StatusNoContent)
				}))

				handler.ServeHTTP(rw, r)

				if test.allowed {
					require.Equal(t, test.origin, rw.Header().Get("Access-Control-Allow-Origin"))
				} else {
					require.Equal(t, "", rw.Header().Get("Access-Control-Allow-Origin"))
				}

				// For options we should never get to our handler as the middleware
				// short-circuits with a 200.
				if method == http.MethodOptions {
					require.Equal(t, http.StatusOK, rw.Code)
				} else {
					require.Equal(t, http.StatusNoContent, rw.Code)
				}
			}
		})
	}
}

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

	const origin = "https://app.example.com"

	handler := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
		rw.WriteHeader(http.StatusNoContent)
	})

	// send returns the recorded response for a cross-origin request. An
	// OPTIONS request is sent as a preflight for a POST.
	send := func(t *testing.T, allowAll bool, method, path string) *httptest.ResponseRecorder {
		t.Helper()
		r := httptest.NewRequest(method, path, nil)
		r.Header.Set("Origin", origin)
		if method == http.MethodOptions {
			r.Header.Set("Access-Control-Request-Method", http.MethodPost)
		}
		rw := httptest.NewRecorder()
		httpmw.Cors(allowAll)(handler).ServeHTTP(rw, r)
		return rw
	}

	tests := []struct {
		name       string
		path       string
		permissive bool
	}{
		{name: "Authorize", path: "/oauth2/authorize", permissive: false},
		{name: "AuthorizeTrailingSlash", path: "/oauth2/authorize/", permissive: false},
		{name: "AuthorizeSubpath", path: "/oauth2/authorize/extra", permissive: false},
		{name: "AuthorizeRepeatedSlash", path: "/oauth2//authorize", permissive: false},
		{name: "AuthorizeLeadingRepeatedSlash", path: "//oauth2/authorize", permissive: false},
		{name: "Tokens", path: "/oauth2/tokens", permissive: true},
		{name: "Revoke", path: "/oauth2/revoke", permissive: true},
		{name: "Register", path: "/oauth2/register", permissive: true},
		{name: "Clients", path: "/oauth2/clients/abc", permissive: true},
		{name: "AuthorizationServerMetadata", path: "/.well-known/oauth-authorization-server", permissive: true},
		{name: "ProtectedResourceMetadata", path: "/.well-known/oauth-protected-resource", permissive: true},
		{name: "MCP", path: "/api/v2/mcp/http", permissive: true},
		{name: "API", path: "/api/v2/users/me", permissive: false},
	}

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

			for _, method := range []string{http.MethodGet, http.MethodPost, http.MethodOptions} {
				rw := send(t, false, method, test.path)

				if test.permissive {
					require.Equal(t, "*", rw.Header().Get("Access-Control-Allow-Origin"), method)
					if method == http.MethodOptions {
						require.Equal(t, "86400", rw.Header().Get("Access-Control-Max-Age"))
					}
				} else {
					for name := range rw.Header() {
						require.False(t, strings.HasPrefix(name, "Access-Control-"), "%s %s sent %s", method, test.path, name)
					}
				}

				// The middleware answers preflights itself with a 200.
				if method == http.MethodOptions {
					require.Equal(t, http.StatusOK, rw.Code)
				} else {
					require.Equal(t, http.StatusNoContent, rw.Code)
				}
			}
		})
	}

	// With every origin allowed, the authorization endpoint behaves like the
	// rest of the site.
	t.Run("AllowAll", func(t *testing.T) {
		t.Parallel()

		rw := send(t, true, http.MethodGet, "/oauth2/authorize")
		require.Equal(t, "*", rw.Header().Get("Access-Control-Allow-Origin"))
	})
}