82 lines
3.2 KiB
Go
82 lines
3.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/adminjob"
|
|
"github.com/Silo-Server/silo-server/internal/auth"
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
|
)
|
|
|
|
func TestCanReadAdminJob_AdminCanReadAnyJob(t *testing.T) {
|
|
claims := &auth.Claims{UserID: 1, Role: "admin"}
|
|
job := &models.AdminJob{CreatedByUserID: 2, JobType: adminjob.JobTypeCatalogExport}
|
|
if !canReadAdminJob(claims, job) {
|
|
t.Fatal("admin should be allowed to read any job")
|
|
}
|
|
}
|
|
|
|
func TestCanReadAdminJob_CreatorCanReadOwnItemRefreshJob(t *testing.T) {
|
|
claims := &auth.Claims{UserID: 2, Role: "user"}
|
|
job := &models.AdminJob{CreatedByUserID: 2, JobType: adminjob.JobTypeItemRefresh}
|
|
if !canReadAdminJob(claims, job) {
|
|
t.Fatal("creator should be allowed to read own item refresh job")
|
|
}
|
|
}
|
|
|
|
func TestCanReadAdminJob_CreatorCannotReadOwnNonItemRefreshJob(t *testing.T) {
|
|
claims := &auth.Claims{UserID: 2, Role: "user"}
|
|
job := &models.AdminJob{CreatedByUserID: 2, JobType: adminjob.JobTypeCatalogExport}
|
|
if canReadAdminJob(claims, job) {
|
|
t.Fatal("non-admin should not read non-item-refresh jobs")
|
|
}
|
|
}
|
|
|
|
func TestCanReadAdminJob_OtherUserCannotReadItemRefreshJob(t *testing.T) {
|
|
claims := &auth.Claims{UserID: 3, Role: "user"}
|
|
job := &models.AdminJob{CreatedByUserID: 2, JobType: adminjob.JobTypeItemRefresh}
|
|
if canReadAdminJob(claims, job) {
|
|
t.Fatal("non-admin should not read another user's item refresh job")
|
|
}
|
|
}
|
|
|
|
func TestAdminJobToResponseForClaims_NonAdminSanitizesItemRefreshPayloads(t *testing.T) {
|
|
claims := &auth.Claims{UserID: 2, Role: "user"}
|
|
job := &models.AdminJob{
|
|
JobType: adminjob.JobTypeItemRefresh,
|
|
CreatedByUserID: 2,
|
|
RequestPayload: json.RawMessage(
|
|
`{"requested_content_id":"item-1","scan_path":"/srv/media/private/movie"}`,
|
|
),
|
|
ResultPayload: json.RawMessage(
|
|
`{"requested_content_id":"item-1","detail_content_id":"item-2","scan_path":"/srv/media/private/movie","scan_result":{"New":1,"RootObservations":[{"RootPath":"/srv/media/private","SampleFilePath":"/srv/media/private/movie.mkv"}]}}`,
|
|
),
|
|
ErrorMessage: "scan scope: stat /srv/media/private/movie: permission denied",
|
|
PublicURL: "https://example.test/public",
|
|
}
|
|
|
|
resp := adminJobToResponseForClaims(nil, job, nil, claims)
|
|
|
|
if string(resp.RequestPayload) != `{}` {
|
|
t.Fatalf("RequestPayload = %s, want sanitized empty object", resp.RequestPayload)
|
|
}
|
|
if resp.PublicURL != "" || resp.DownloadURL != "" || resp.DownloadExpiresAt != nil {
|
|
t.Fatalf("expected non-admin URLs to be stripped, got public=%q download=%q", resp.PublicURL, resp.DownloadURL)
|
|
}
|
|
if bytes.Contains(resp.ResultPayload, []byte("/srv/media")) ||
|
|
bytes.Contains(resp.ResultPayload, []byte("scan_path")) ||
|
|
bytes.Contains(resp.ResultPayload, []byte("RootObservations")) ||
|
|
bytes.Contains(resp.ResultPayload, []byte("SampleFilePath")) {
|
|
t.Fatalf("ResultPayload leaked sensitive data: %s", resp.ResultPayload)
|
|
}
|
|
if !bytes.Contains(resp.ResultPayload, []byte("requested_content_id")) ||
|
|
!bytes.Contains(resp.ResultPayload, []byte("detail_content_id")) {
|
|
t.Fatalf("ResultPayload = %s, want safe item refresh summary fields", resp.ResultPayload)
|
|
}
|
|
if resp.ErrorMessage != "" {
|
|
t.Fatalf("ErrorMessage = %q, want stripped for non-admin", resp.ErrorMessage)
|
|
}
|
|
}
|