Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

153 lines
3.9 KiB
Go

package company
import (
"bytes"
"image"
"image/png"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/descrybe/descrybe-v2/apps/api/internal/security"
"github.com/google/uuid"
)
func TestValidateLogoURL_HostedAndHTTPS(t *testing.T) {
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
name := "22222222-2222-2222-2222-222222222222.png"
got, err := ValidateLogoURL(BrandLogoURLPrefix+name, cid)
if err != nil || got != BrandLogoURLPrefix+name {
t.Fatalf("hosted: got=%q err=%v", got, err)
}
got, err = ValidateLogoURL("https://example.com/logo.png", cid)
if err != nil || !strings.HasPrefix(got, "https://") {
t.Fatalf("https: got=%q err=%v", got, err)
}
_, err = ValidateLogoURL(BrandLogoURLPrefix+"../etc/passwd", cid)
if err == nil {
t.Fatal("expected traversal reject")
}
_, err = ValidateLogoURL("/api/brand/logo/files/not-a-uuid.png", cid)
if err == nil {
t.Fatal("expected invalid name reject")
}
_, err = ValidateLogoURL("https://192.168.1.5/logo.png", cid)
if err == nil || !(err == security.ErrBlockedURL || err == security.ErrBlockedHost) {
t.Fatalf("expected blocked private host, got %v", err)
}
}
func TestSaveAndResolveBrandLogo(t *testing.T) {
dir := t.TempDir()
cid := uuid.New()
var buf bytes.Buffer
img := image.NewRGBA(image.Rect(0, 0, 8, 8))
if err := png.Encode(&buf, img); err != nil {
t.Fatal(err)
}
logoURL, abs, ct, size, err := SaveBrandLogo(dir, cid, "mark.png", "image/png", bytes.NewReader(buf.Bytes()))
if err != nil {
t.Fatal(err)
}
if ct != "image/png" || size <= 0 {
t.Fatalf("ct=%s size=%d", ct, size)
}
name, ok := HostedLogoFilename(logoURL)
if !ok {
t.Fatalf("logoURL=%s", logoURL)
}
if _, err := os.Stat(abs); err != nil {
t.Fatal(err)
}
resolved, err := ResolveBrandLogoPath(dir, cid, name)
if err != nil {
t.Fatal(err)
}
if filepath.Clean(resolved) != filepath.Clean(abs) {
t.Fatalf("resolved=%s abs=%s", resolved, abs)
}
// Wrong company must not resolve another company's file via path tricks.
other := uuid.New()
_, err = ResolveBrandLogoPath(dir, other, name)
if err != nil {
// file simply missing for other company is fine; open should 404
}
_, _, err = OpenBrandLogo(dir, other, name)
if err != ErrLogoNotFound {
t.Fatalf("expected not found for other company, got %v", err)
}
// Reject path traversal names.
_, err = ResolveBrandLogoPath(dir, cid, "../../etc/passwd")
if err != ErrLogoInvalidName {
t.Fatalf("got %v", err)
}
}
func TestSaveBrandLogo_RejectsNonImage(t *testing.T) {
dir := t.TempDir()
_, _, _, _, err := SaveBrandLogo(dir, uuid.New(), "x.png", "image/png", strings.NewReader("not-an-image"))
if err != ErrLogoInvalidType {
t.Fatalf("got %v", err)
}
}
func TestSignAndVerifyPublicBrandLogo(t *testing.T) {
cid := uuid.New()
name := uuid.New().String() + ".png"
secret := "test-secret"
u, err := SignPublicBrandLogoURL("https://api.example.com", secret, cid, name, time.Hour)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(u, PublicBrandLogoPathPrefix) {
t.Fatalf("url=%s", u)
}
// Parse query
exp := time.Now().Add(time.Hour).Unix()
sig := signBrandLogo(secret, cid, name, exp)
// Use exact exp from signed URL
parts := strings.Split(u, "?")
if len(parts) != 2 {
t.Fatalf("url=%s", u)
}
q := map[string]string{}
for _, kv := range strings.Split(parts[1], "&") {
p := strings.SplitN(kv, "=", 2)
if len(p) == 2 {
q[p[0]] = p[1]
}
}
expVal := mustParseInt(t, q["exp"])
if err := VerifyPublicBrandLogoSig(secret, cid, name, expVal, q["sig"]); err != nil {
t.Fatal(err)
}
if err := VerifyPublicBrandLogoSig(secret, cid, name, expVal, "deadbeef"); err != ErrLogoBadSig {
t.Fatalf("got %v", err)
}
_ = sig
}
func mustParseInt(t *testing.T, s string) int64 {
t.Helper()
var n int64
for _, c := range s {
if c < '0' || c > '9' {
t.Fatalf("bad int %q", s)
}
n = n*10 + int64(c-'0')
}
return n
}