413 lines
9.8 KiB
Go
413 lines
9.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"seanime/internal/extension"
|
|
"testing"
|
|
|
|
"github.com/samber/mo"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Mock AppContextImpl for testing
|
|
type mockAppContext struct {
|
|
AppContextImpl
|
|
mockPaths map[string][]string
|
|
}
|
|
|
|
// Create a new mock context with initialized fields
|
|
func newMockAppContext(paths map[string][]string) *mockAppContext {
|
|
ctx := &mockAppContext{
|
|
mockPaths: paths,
|
|
}
|
|
// Initialize the animeLibraryPaths field with mock data
|
|
if libraryPaths, ok := paths["SEANIME_ANIME_LIBRARY"]; ok {
|
|
ctx.animeLibraryPaths = mo.Some(libraryPaths)
|
|
} else {
|
|
ctx.animeLibraryPaths = mo.Some([]string{})
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
func TestIsAllowedPath(t *testing.T) {
|
|
// Create mock context with predefined paths
|
|
mockCtx := newMockAppContext(map[string][]string{
|
|
"SEANIME_ANIME_LIBRARY": {"/anime/lib1", "/anime/lib2"},
|
|
"HOME": {"/home/user"},
|
|
"TEMP": {"/tmp"},
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
ext *extension.Extension
|
|
path string
|
|
mode int
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "nil extension",
|
|
ext: nil,
|
|
path: "/some/path",
|
|
mode: AllowPathRead,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "no patterns",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/some/path",
|
|
mode: AllowPathRead,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "simple path match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
ReadPaths: []string{"/test/*.txt"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/test/file.txt",
|
|
mode: AllowPathRead,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple library paths - first match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/anime/lib1/file.txt",
|
|
mode: AllowPathRead,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple library paths - second match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/anime/lib2/file.txt",
|
|
mode: AllowPathRead,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "write mode with read pattern",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/test/file.txt",
|
|
mode: AllowPathWrite,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple patterns - match one",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/anime/lib1/file.txt",
|
|
mode: AllowPathRead,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "no matching pattern",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
path: "/anime/lib1/file.txt",
|
|
mode: AllowPathRead,
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := mockCtx.isAllowedPath(tt.ext, tt.path, tt.mode)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsAllowedCommand(t *testing.T) {
|
|
// Create mock context
|
|
mockCtx := newMockAppContext(map[string][]string{
|
|
"HOME": {"/home/user"},
|
|
"SEANIME_ANIME_LIBRARY": {}, // Empty but initialized
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
ext *extension.Extension
|
|
cmd string
|
|
args []string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "nil extension",
|
|
ext: nil,
|
|
cmd: "ls",
|
|
args: []string{"-l"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "simple command no args",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "ls",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "ls",
|
|
args: []string{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "command with fixed args - match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "git",
|
|
Args: []extension.CommandArg{
|
|
{Value: "pull"},
|
|
{Value: "origin"},
|
|
{Value: "main"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "git",
|
|
args: []string{"pull", "origin", "main"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "command with fixed args - no match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "git",
|
|
Args: []extension.CommandArg{
|
|
{Value: "pull"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "git",
|
|
args: []string{"push"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "command with $ARGS validator",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "echo",
|
|
Args: []extension.CommandArg{
|
|
{Validator: "$ARGS"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "echo",
|
|
args: []string{"hello", "world"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "command with regex validator - match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "open",
|
|
Args: []extension.CommandArg{
|
|
{Validator: "^https?://.*$"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "open",
|
|
args: []string{"https://example.com"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "command with regex validator - no match",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "open",
|
|
Args: []extension.CommandArg{
|
|
{Validator: "^https?://.*$"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "open",
|
|
args: []string{"file://example.com"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "command with $PATH validator",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "open",
|
|
Args: []extension.CommandArg{
|
|
{Validator: "$PATH"},
|
|
},
|
|
},
|
|
},
|
|
WritePaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "open",
|
|
args: []string{"/anime/lib1/test.txt"},
|
|
expected: false, // Directory does not exist on the machine
|
|
},
|
|
{
|
|
name: "too many args",
|
|
ext: &extension.Extension{
|
|
Plugin: &extension.PluginManifest{
|
|
Permissions: extension.PluginPermissions{
|
|
Scopes: []extension.PluginPermissionScope{
|
|
extension.PluginPermissionSystem,
|
|
},
|
|
Allow: extension.PluginAllowlist{
|
|
CommandScopes: []extension.CommandScope{
|
|
{
|
|
Command: "ls",
|
|
Args: []extension.CommandArg{
|
|
{Value: "-l"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cmd: "ls",
|
|
args: []string{"-l", "-a"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := mockCtx.isAllowedCommand(tt.ext, tt.cmd, tt.args...)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|