Files
im_server/server/hdl_files_test.go
T
2026-08-14 23:37:31 +08:00

126 lines
4.0 KiB
Go

package main
import (
"encoding/base64"
"net/http/httptest"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/auth/mock_auth"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/mock_store"
"github.com/tinode/chat/server/store/types"
)
func TestAuthFileRequestFromSessionRequiresTenant(t *testing.T) {
oldSessionStore := globals.sessionStore
globals.sessionStore = NewSessionStore(time.Minute)
defer func() { globals.sessionStore = oldSessionStore }()
sess, _ := globals.sessionStore.NewSession(httptest.NewRecorder(), "file-test-sid")
sess.uid = types.Uid(42)
if _, _, _, err := authFileRequest("", "", sess.sid, ""); err != types.ErrFailed {
t.Fatalf("session without tenant: got %v, want %v", err, types.ErrFailed)
}
sess.tenantID = types.TenantID(7)
principal, challenge, _, err := authFileRequest("", "", sess.sid, "")
if err != nil {
t.Fatal(err)
}
if challenge != nil {
t.Fatal("unexpected challenge")
}
if principal.TenantID != sess.tenantID || principal.UID != sess.uid {
t.Fatalf("principal mismatch: got %+v, want tenant=%d uid=%s", principal, sess.tenantID, sess.uid)
}
}
func TestAuthFileRequestFromTokenReturnsPrincipal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldStore := store.Store
defer func() { store.Store = oldStore }()
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth)
tokenAuth.EXPECT().IsInitialized().Return(true)
tokenAuth.EXPECT().Authenticate(auth.AuthContext{RemoteAddr: "203.0.113.10"}, []byte("token-1")).
Return(&auth.Rec{TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth}, nil, nil)
store.Store = mockStore
principal, challenge, _, err := authFileRequest("token", base64.StdEncoding.EncodeToString([]byte("token-1")), "", "203.0.113.10")
if err != nil {
t.Fatal(err)
}
if challenge != nil {
t.Fatal("unexpected challenge")
}
if principal.TenantID != 7 || principal.UID != 42 {
t.Fatalf("principal mismatch: %#v", principal)
}
}
func TestAuthFileRequestAllowsUnauthenticatedNewAccountPath(t *testing.T) {
principal, challenge, _, err := authFileRequest("", "", "", "")
if err != nil {
t.Fatal(err)
}
if challenge != nil {
t.Fatal("unexpected challenge")
}
if !principal.TenantID.IsZero() || !principal.UID.IsZero() {
t.Fatalf("principal should be empty for unauthenticated request: %+v", principal)
}
}
func TestAuthorizeLargeFileDownloadDenied(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldFiles := store.Files
files := mock_store.NewMockFilePersistenceInterface(ctrl)
store.Files = files
defer func() { store.Files = oldFiles }()
fd := &types.FileDef{
ObjHeader: types.ObjHeader{Id: types.Uid(100).String(), UpdatedAt: time.Date(2026, 8, 13, 9, 0, 0, 0, time.UTC)},
TenantID: 7,
Status: types.UploadCompleted,
MimeType: "text/plain",
Location: "notes.txt",
}
files.EXPECT().UserHasAccess(types.TenantID(7), types.Uid(100).String(), types.Uid(42)).Return(false, nil)
if err := authorizeLargeFileDownload(7, 42, "", fd); err != types.ErrPermissionDenied {
t.Fatalf("expected permission denied, got %v", err)
}
}
func TestAuthorizeLargeFileDownloadRejectsCrossTenant(t *testing.T) {
fd := &types.FileDef{
ObjHeader: types.ObjHeader{Id: types.Uid(100).String(), UpdatedAt: time.Date(2026, 8, 13, 9, 0, 0, 0, time.UTC)},
TenantID: 8,
Status: types.UploadCompleted,
MimeType: "text/plain",
Location: "notes.txt",
}
if err := authorizeLargeFileDownload(7, 42, "", fd); err != types.ErrNotFound {
t.Fatalf("expected not found for cross-tenant file, got %v", err)
}
}
func TestContentDispositionAttachmentSanitizesName(t *testing.T) {
fd := &types.FileDef{
ObjHeader: types.ObjHeader{Id: types.Uid(100).String()},
Location: `../bad"name.txt`,
}
got := contentDispositionAttachment(fd)
if got != `attachment; filename=badname.txt` {
t.Fatalf("unexpected content disposition: %s", got)
}
}