Files
wanderer/db/pluginsystem/policy_test.go
slothful-vassal 485ec53f6d feat: add plugin system (#1034)
* feat: add plugin system

* fix db docker build

* fix hammerhead readme, add strava subscription news to docs

* fixes and sdk improvements

* fix: reduce Meilisearch load, debounce federation sync (#1012)

* optimize meili trail index

* several fixes

---------

Co-authored-by: Flomp <Flomp@users.noreply.github.com>

* Bump svelte from 5.55.5 to 5.56.0 in /docs (#1032)

Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.55.5 to 5.56.0.
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.56.0/packages/svelte)

---
updated-dependencies:
- dependency-name: svelte
  dependency-version: 5.56.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Flomp <Flomp@users.noreply.github.com>

* Release v0.19.2 (#1035)

* chore: release v0.19.2

* add changelog

---------

Co-authored-by: Flomp <26000991+Flomp@users.noreply.github.com>
Co-authored-by: Christian Beutel <>

* speed up plugin sync and several small fixes

* concepts for security improvements and process stability

* improve concept

* security concept implemented

* remove insecure TLS

* worker concept implemented

* fixes and cleanup

* fixes

* docu

* mermaid, namings

* WASM plugin host improvements, plugin logging

* fix db migration

* Improve plugin config and category mapping UI

* fixes

* further fixes

* remove manual test sync

* fix db migration and strava mapping

* type added, UI improvements

* fix plugin card toggle clickable area

* optimize synch status card layout

* plugin type 'trails' instead of 'integration'

* session auth validation in UI

* fix komoot date and waypoints

* improve category mapping

* fix send to hammerhead: trail name

* plugin setup error handling improved

* fix review findings

* re-mapping added

* rename remote_category

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Flomp <Flomp@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Flomp <26000991+Flomp@users.noreply.github.com>
2026-06-22 15:00:44 +02:00

170 lines
5.2 KiB
Go

package pluginsystem
import (
"net/url"
"testing"
)
func TestValidateHostRequestSpecAcceptsConnectorAndAuthReference(t *testing.T) {
manifest := hammerheadManifestForTest()
spec := HostRequestSpec{
Method: "POST",
Target: RequestTarget{
Type: "connector",
Connector: "api",
Path: "/v1/users/123/routes/import/file",
},
Auth: "provider_session",
Expect: ResponseExpect{
ContentTypes: []string{"application/json"},
MaxBytes: 1024,
},
}
manifest.Permissions.Downloads.ContentTypes = append(manifest.Permissions.Downloads.ContentTypes, "application/json")
manifest.Permissions.Downloads.MaxBytes = 2048
if err := ValidateHostRequestSpec(manifest, spec, testPolicy()); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestValidateHostRequestSpecRejectsUnknownConnector(t *testing.T) {
manifest := hammerheadManifestForTest()
spec := HostRequestSpec{
Method: "GET",
Target: RequestTarget{Type: "connector", Connector: "evil", Path: "/v1"},
}
if err := ValidateHostRequestSpec(manifest, spec, testPolicy()); err == nil {
t.Fatal("expected error")
}
}
func TestValidateHostRequestSpecRejectsPathScopeEscape(t *testing.T) {
manifest := hammerheadManifestForTest()
spec := HostRequestSpec{
Method: "GET",
Target: RequestTarget{Type: "connector", Connector: "api", Path: "/v1-evil"},
}
if err := ValidateHostRequestSpec(manifest, spec, testPolicy()); err == nil {
t.Fatal("expected error")
}
}
func TestValidateHostRequestSpecRejectsLimitExpansion(t *testing.T) {
manifest := hammerheadManifestForTest()
manifest.Permissions.Downloads.MaxBytes = 100
spec := HostRequestSpec{
Method: "GET",
Target: RequestTarget{Type: "connector", Connector: "api", Path: "/v1/users"},
Expect: ResponseExpect{MaxBytes: 101},
}
if err := ValidateHostRequestSpec(manifest, spec, testPolicy()); err == nil {
t.Fatal("expected error")
}
}
func TestBuildConnectorURLPreservesBasePathAndQueryOrder(t *testing.T) {
target := ResolvedConnectorTarget{
Name: "immich",
BaseURL: "https://photos.example.test:8443",
BasePath: "/immich",
AllowedPathPrefixes: []string{"/api"},
}
u, err := BuildConnectorURL(target, "/api/assets/1/original", []QueryParam{
{Name: "z", Value: "last"},
{Name: "key", Value: "a"},
{Name: "key", Value: "b"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if u.String() != "https://photos.example.test:8443/immich/api/assets/1/original?z=last&key=a&key=b" {
t.Fatalf("unexpected url: %s", u.String())
}
if err := ValidateConnectorURL(target, u); err != nil {
t.Fatalf("unexpected scope error: %v", err)
}
}
func TestBuildConnectorURLPreservesTrailingSlash(t *testing.T) {
target := ResolvedConnectorTarget{
Name: "komoot",
BaseURL: "https://api.komoot.de",
BasePath: "/",
AllowedPathPrefixes: []string{"/v006"},
}
u, err := BuildConnectorURL(target, "/v006/account/email/user%40example.test/", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if u.String() != "https://api.komoot.de/v006/account/email/user@example.test/" {
t.Fatalf("unexpected url: %s", u.String())
}
}
func TestConnectorPathNormalizationRejectsAmbiguousEscapes(t *testing.T) {
for _, candidate := range []string{"/api%2fadmin", "/api/%252e%252e/admin", "/api/../admin"} {
t.Run(candidate, func(t *testing.T) {
target := ResolvedConnectorTarget{
Name: "api",
BaseURL: "https://example.test",
BasePath: "/",
AllowedPathPrefixes: []string{"/api"},
}
u, err := BuildConnectorURL(target, candidate, nil)
if err == nil {
err = ValidateConnectorURL(target, u)
}
if err == nil {
t.Fatal("expected scope error")
}
})
}
}
func TestConnectorStorageRedirectOriginReturnsMatchedOriginPolicy(t *testing.T) {
connector := ResolvedConnectorTarget{
Name: "immich",
BaseURL: "https://photos.example.test",
BasePath: "/immich",
SupportsStorageRedirects: true,
StorageOrigins: map[string]ResolvedConnectorOrigin{
"minio": {
Name: "minio",
BaseURL: "https://storage.example.test:9443",
BasePath: "/assets",
AllowPrivate: true,
TLS: ConnectorTLSConfig{Mode: TLSModeCustomCA, CABundle: []byte("ca")},
},
},
}
initial, _ := BuildConnectorURL(connector, "/api/assets/1/original", nil)
redirected, _ := url.Parse("https://storage.example.test:9443/assets/bucket/photo.jpg")
origin, err := ConnectorStorageRedirectOrigin(connector, initial, redirected)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if origin.Name != "minio" || !origin.AllowPrivate || origin.TLS.Mode != TLSModeCustomCA {
t.Fatalf("unexpected origin policy: %#v", origin)
}
}
func testPolicy() RequestPolicyContext {
return RequestPolicyContext{
Connectors: map[string]ResolvedConnectorTarget{
"api": {
Name: "api",
Type: ConnectorTypePublicAPI,
BaseURL: "https://dashboard.hammerhead.io",
BasePath: "/",
AllowedPathPrefixes: []string{"/v1"},
Auth: []string{"provider_session"},
},
},
}
}