Ctrl K
ComunidadGuías…

internal/pluginstore/auth_test.go

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

Archivo original ↗Preguntar a KiBot
package pluginstore

import (
	"context"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"
)

func TestPluginStoreAuthMatchesURLHostAndPathBoundaries(t *testing.T) {
	t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
	auth := []AuthConfig{{
		Match:    "https://downloads.example/private",
		ApplyTo:  []string{RequestKindArtifact},
		Type:     AuthTypeBearer,
		TokenEnv: "PLUGIN_STORE_TOKEN",
	}}

	tests := []struct {
		name     string
		url      string
		wantAuth bool
	}{
		{name: "exact path", url: "https://downloads.example/private", wantAuth: true},
		{name: "child path", url: "https://downloads.example/private/plugin.zip", wantAuth: true},
		{name: "sibling prefix", url: "https://downloads.example/private2/plugin.zip", wantAuth: false},
		{name: "similar host", url: "https://downloads.example.evil/private/plugin.zip", wantAuth: false},
		{name: "different scheme", url: "http://downloads.example/private/plugin.zip", wantAuth: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			headers := http.Header{}
			if errAuth := applyPluginStoreAuth(headers, auth, tt.url, RequestKindArtifact); errAuth != nil {
				t.Fatalf("applyPluginStoreAuth() error = %v", errAuth)
			}
			gotAuth := headers.Get("Authorization") != ""
			if gotAuth != tt.wantAuth {
				t.Fatalf("Authorization set = %v, want %v", gotAuth, tt.wantAuth)
			}
		})
	}
}

func TestPluginStoreGitHubTokenUsesExplicitTokenEnv(t *testing.T) {
	t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
	headers := http.Header{}
	auth := []AuthConfig{{
		Match:    "https://api.github.com/repos/author-name/sample-provider/releases/",
		ApplyTo:  []string{RequestKindArtifact},
		Type:     AuthTypeGitHubToken,
		TokenEnv: "PLUGIN_STORE_TOKEN",
	}}

	if errAuth := applyPluginStoreAuth(headers, auth, "https://api.github.com/repos/author-name/sample-provider/releases/assets/1", RequestKindArtifact); errAuth != nil {
		t.Fatalf("applyPluginStoreAuth() error = %v", errAuth)
	}
	if gotAuth := headers.Get("Authorization"); gotAuth != "Bearer secret-token" {
		t.Fatalf("Authorization = %q, want Bearer secret-token", gotAuth)
	}
}

func TestPluginAuthConfiguredCoversInstallRequestKinds(t *testing.T) {
	t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")

	source := Source{URL: "https://registry.example/registry.json"}
	directPlugin := Plugin{
		ID:      "sample-provider",
		Version: "1.0.0",
		Install: InstallPlan{
			Type: InstallTypeDirect,
			Artifacts: []Artifact{{
				GOOS:   "linux",
				GOARCH: "amd64",
				URL:    "https://downloads.example/private/sample-provider.zip",
				SHA256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			}},
		},
	}
	gitHubPlugin := Plugin{
		ID:         "sample-provider",
		Repository: "https://github.com/author-name/sample-provider",
	}

	tests := []struct {
		name   string
		plugin Plugin
		auth   []AuthConfig
	}{
		{
			name:   "registry",
			plugin: gitHubPlugin,
			auth: []AuthConfig{{
				Match:    "https://registry.example/",
				ApplyTo:  []string{RequestKindRegistry},
				Type:     AuthTypeBearer,
				TokenEnv: "PLUGIN_STORE_TOKEN",
			}},
		},
		{
			name:   "direct artifact",
			plugin: directPlugin,
			auth: []AuthConfig{{
				Match:    "https://downloads.example/private/",
				ApplyTo:  []string{RequestKindArtifact},
				Type:     AuthTypeBearer,
				TokenEnv: "PLUGIN_STORE_TOKEN",
			}},
		},
		{
			name:   "github metadata",
			plugin: gitHubPlugin,
			auth: []AuthConfig{{
				Match:    "https://api.github.com/repos/author-name/sample-provider/releases/",
				ApplyTo:  []string{RequestKindMetadata},
				Type:     AuthTypeBearer,
				TokenEnv: "PLUGIN_STORE_TOKEN",
			}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if !PluginAuthConfigured(source, tt.plugin, tt.auth) {
				t.Fatal("PluginAuthConfigured() = false, want true")
			}
		})
	}
}

func TestPluginStoreAuthHeaderIsReevaluatedAcrossRedirect(t *testing.T) {
	t.Setenv("PLUGIN_STORE_HEADER", "secret-token")

	var initialHeader string
	var redirectedHeader string
	artifactData := []byte("artifact-data")
	sum := sha256.Sum256(artifactData)
	target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		redirectedHeader = r.Header.Get("X-Plugin-Token")
		_, _ = w.Write(artifactData)
	}))
	t.Cleanup(target.Close)
	source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		initialHeader = r.Header.Get("X-Plugin-Token")
		http.Redirect(w, r, target.URL+"/artifact.zip", http.StatusFound)
	}))
	t.Cleanup(source.Close)

	client := Client{
		HTTPClient: source.Client(),
		Auth: []AuthConfig{
			{
				Match:          source.URL + "/private/",
				ApplyTo:        []string{RequestKindArtifact},
				Type:           AuthTypeHeader,
				HeaderName:     "X-Plugin-Token",
				HeaderValueEnv: "PLUGIN_STORE_HEADER",
				AllowInsecure:  true,
			},
			{
				Match:         target.URL + "/",
				ApplyTo:       []string{RequestKindArtifact},
				Type:          AuthTypeNone,
				AllowInsecure: true,
			},
		},
	}
	data, errDownload := client.DownloadArtifact(context.Background(), Artifact{
		GOOS:   "linux",
		GOARCH: "amd64",
		URL:    source.URL + "/private/artifact.zip",
		SHA256: hex.EncodeToString(sum[:]),
	})
	if errDownload != nil {
		t.Fatalf("DownloadArtifact() error = %v", errDownload)
	}
	if string(data) != string(artifactData) {
		t.Fatalf("DownloadArtifact() = %q, want %q", data, artifactData)
	}
	if initialHeader != "secret-token" {
		t.Fatalf("initial auth header = %q, want secret-token", initialHeader)
	}
	if redirectedHeader != "" {
		t.Fatalf("redirected auth header = %q, want empty", redirectedHeader)
	}
}

func TestPluginStoreAuthHeaderIsAppliedToMatchingRedirect(t *testing.T) {
	t.Setenv("PLUGIN_STORE_HEADER", "secret-token")

	var redirectedHeader string
	artifactData := []byte("artifact-data")
	sum := sha256.Sum256(artifactData)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/private/start.zip" {
			http.Redirect(w, r, "/private/artifact.zip", http.StatusFound)
			return
		}
		redirectedHeader = r.Header.Get("X-Plugin-Token")
		_, _ = io.WriteString(w, string(artifactData))
	}))
	t.Cleanup(server.Close)

	client := Client{
		HTTPClient: server.Client(),
		Auth: []AuthConfig{{
			Match:          server.URL + "/private/",
			ApplyTo:        []string{RequestKindArtifact},
			Type:           AuthTypeHeader,
			HeaderName:     "X-Plugin-Token",
			HeaderValueEnv: "PLUGIN_STORE_HEADER",
			AllowInsecure:  true,
		}},
	}
	if _, errDownload := client.DownloadArtifact(context.Background(), Artifact{
		GOOS:   "linux",
		GOARCH: "amd64",
		URL:    server.URL + "/private/start.zip",
		SHA256: hex.EncodeToString(sum[:]),
	}); errDownload != nil {
		t.Fatalf("DownloadArtifact() error = %v", errDownload)
	}
	if redirectedHeader != "secret-token" {
		t.Fatalf("redirected auth header = %q, want secret-token", redirectedHeader)
	}
}

func TestResolvedPluginStoreAuthTakesPriorityOverEnvironmentAuth(t *testing.T) {
	t.Setenv("PLUGIN_STORE_TOKEN", "environment-token")
	headers := http.Header{}
	resolved := []ResolvedAuthConfig{{
		Match:   "https://downloads.example/private/",
		ApplyTo: []string{RequestKindArtifact},
		Type:    AuthTypeBearer,
		Token:   Secret("resolved-token"),
	}}
	auth := []AuthConfig{{
		Match:    "https://downloads.example/private/",
		ApplyTo:  []string{RequestKindArtifact},
		Type:     AuthTypeBearer,
		TokenEnv: "PLUGIN_STORE_TOKEN",
	}}

	applied, errApply := applyPluginStoreAuthForClient(headers, resolved, auth, "https://downloads.example/private/plugin.zip", RequestKindArtifact)
	if errApply != nil {
		t.Fatalf("applyPluginStoreAuthForClient() error = %v", errApply)
	}
	if !applied || headers.Get("Authorization") != "Bearer resolved-token" {
		t.Fatalf("Authorization = %q, want resolved token", headers.Get("Authorization"))
	}
}

func TestResolvedNoAuthRuleBlocksEnvironmentFallback(t *testing.T) {
	t.Setenv("PLUGIN_STORE_TOKEN", "environment-token")
	headers := http.Header{}
	resolved := []ResolvedAuthConfig{{
		Match: "https://downloads.example/private/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeNone,
	}}
	auth := []AuthConfig{{
		Match: "https://downloads.example/private/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeBearer, TokenEnv: "PLUGIN_STORE_TOKEN",
	}}

	applied, errApply := applyPluginStoreAuthForClient(headers, resolved, auth, "https://downloads.example/private/plugin.zip", RequestKindArtifact)
	if errApply != nil {
		t.Fatalf("applyPluginStoreAuthForClient() error = %v", errApply)
	}
	if applied || headers.Get("Authorization") != "" {
		t.Fatalf("resolved none rule applied environment auth: %q", headers.Get("Authorization"))
	}
}

func TestResolvedPluginStoreAuthIsNotForwardedAcrossOriginRedirect(t *testing.T) {
	var redirectedAuth string
	target := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		redirectedAuth = r.Header.Get("Authorization")
		_, _ = io.WriteString(w, "artifact")
	}))
	t.Cleanup(target.Close)
	source := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, target.URL+"/artifact.zip", http.StatusFound)
	}))
	t.Cleanup(source.Close)
	client := Client{
		HTTPClient: &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}, //nolint:gosec -- test servers use ephemeral certificates.
		ResolvedAuth: []ResolvedAuthConfig{{
			Match:   source.URL + "/private/",
			ApplyTo: []string{RequestKindArtifact},
			Type:    AuthTypeBearer,
			Token:   Secret("temporary-token"),
		}},
	}

	if _, errDownload := client.DownloadArtifact(context.Background(), Artifact{
		GOOS:   "linux",
		GOARCH: "amd64",
		URL:    source.URL + "/private/artifact.zip",
		SHA256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
	}); errDownload != nil && !strings.Contains(errDownload.Error(), "sha256 mismatch") {
		t.Fatalf("DownloadArtifact() error = %v, want only checksum mismatch", errDownload)
	}
	if redirectedAuth != "" {
		t.Fatalf("redirected Authorization = %q, want empty", redirectedAuth)
	}
}

func TestAuthenticatedPluginStoreFailureDoesNotExposeResponseBody(t *testing.T) {
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "secret diagnostic body", http.StatusUnauthorized)
	}))
	t.Cleanup(server.Close)
	client := Client{
		HTTPClient: server.Client(),
		ResolvedAuth: []ResolvedAuthConfig{{
			Match:   server.URL + "/",
			ApplyTo: []string{RequestKindArtifact},
			Type:    AuthTypeBearer,
			Token:   Secret("temporary-token"),
		}},
	}

	_, errDownload := client.DownloadArtifact(context.Background(), Artifact{
		GOOS:   "linux",
		GOARCH: "amd64",
		URL:    server.URL + "/artifact.zip",
		SHA256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
	})
	if errDownload == nil {
		t.Fatal("DownloadArtifact() error = nil, want unauthorized status")
	}
	if strings.Contains(errDownload.Error(), "secret diagnostic body") {
		t.Fatalf("DownloadArtifact() error leaked response body: %v", errDownload)
	}
}

func TestResolvedAuthClearOverwritesSecrets(t *testing.T) {
	token := Secret("temporary-token")
	backing := token
	auth := ResolvedAuthConfig{Token: token, Username: Secret("user"), Password: Secret("pass"), HeaderValue: Secret("header")}
	auth.Clear()
	for index, value := range backing {
		if value != 0 {
			t.Fatalf("token byte %d = %d, want zero", index, value)
		}
	}
	if auth.Token != nil || auth.Username != nil || auth.Password != nil || auth.HeaderValue != nil {
		t.Fatalf("cleared auth retains secret references: %#v", auth)
	}
}

func TestPluginStoreRequestErrorRedactsQueryAndFragment(t *testing.T) {
	requestURL := "https://user:password@downloads.example/plugin.zip?trace=private-value#section"
	cause := context.Canceled
	errRequest := pluginStoreRequestError(requestURL, &url.Error{URL: requestURL, Err: cause})
	if strings.Contains(errRequest.Error(), "private-value") || strings.Contains(errRequest.Error(), "section") || strings.Contains(errRequest.Error(), "trace=") || strings.Contains(errRequest.Error(), "password") || strings.Contains(errRequest.Error(), "user@") {
		t.Fatalf("pluginStoreRequestError() leaked URL query or fragment: %v", errRequest)
	}
	if !strings.Contains(errRequest.Error(), "https://downloads.example/plugin.zip") {
		t.Fatalf("pluginStoreRequestError() = %v, want sanitized URL", errRequest)
	}
	if !errors.Is(errRequest, cause) {
		t.Fatalf("errors.Is(pluginStoreRequestError(), context.Canceled) = false")
	}
}

func TestPluginStoreRequestURLRejectsCredentials(t *testing.T) {
	errValidate := validatePluginStoreRequestURL(nil, "https://user:password@downloads.example/plugin.zip", RequestKindArtifact)
	if errValidate == nil {
		t.Fatal("validatePluginStoreRequestURL() error = nil, want URL credentials rejection")
	}
	if strings.Contains(errValidate.Error(), "password") {
		t.Fatalf("validatePluginStoreRequestURL() error leaked URL credentials: %v", errValidate)
	}
}

func TestResolvedAuthExpiryRejectsAuthenticatedRequest(t *testing.T) {
	auth := []ResolvedAuthConfig{{
		Match:   "https://downloads.example/private/",
		ApplyTo: []string{RequestKindArtifact},
		Type:    AuthTypeBearer,
		Token:   Secret("temporary-token"),
	}}
	now := time.Now().UTC()
	client := Client{
		HTTPClient:            failingHTTPDoer{},
		ResolvedAuth:          auth,
		ResolvedAuthExpiresAt: now.Add(-time.Second),
	}
	_, errDownload := client.DownloadArtifact(context.Background(), Artifact{
		GOOS:   "linux",
		GOARCH: "amd64",
		URL:    "https://downloads.example/private/plugin.zip",
		SHA256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
	})
	if errDownload == nil || !strings.Contains(errDownload.Error(), "resolved auth expired") {
		t.Fatalf("DownloadArtifact() error = %v, want resolved auth expiry", errDownload)
	}
}