126 lines
3.7 KiB
Go
126 lines
3.7 KiB
Go
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
publicmanifest "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/manifest"
|
|
)
|
|
|
|
const DefaultSiloAPIVersion = "v1"
|
|
|
|
func ValidateManifest(manifest *pluginv1.PluginManifest) error {
|
|
if err := validateManifestShared(manifest); err != nil {
|
|
return err
|
|
}
|
|
if err := validateHostShellPageContract(manifest); err != nil {
|
|
return err
|
|
}
|
|
if manifest.GetChecksum() == "" {
|
|
return fmt.Errorf("plugin manifest checksum is required")
|
|
}
|
|
if len(manifest.GetSupportedPlatforms()) == 0 {
|
|
return fmt.Errorf("plugin manifest supported_platforms is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateCatalogManifest(manifest *pluginv1.PluginManifest) error {
|
|
return validateManifestShared(manifest)
|
|
}
|
|
|
|
func LoadManifestFile(path string) (*pluginv1.PluginManifest, error) {
|
|
manifest, err := publicmanifest.LoadFromDisk(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load manifest file %q: %w", path, err)
|
|
}
|
|
if err := ValidateManifest(manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
return manifest, nil
|
|
}
|
|
|
|
func InstalledManifestPath(installPath string) string {
|
|
return filepath.Join(filepath.Dir(installPath), "manifest.json")
|
|
}
|
|
|
|
func validateRelativePath(path string, label string) error {
|
|
if path == "" {
|
|
return fmt.Errorf("%s path is required", label)
|
|
}
|
|
cleaned := filepath.Clean(path)
|
|
if filepath.IsAbs(cleaned) || cleaned == "." || strings.HasPrefix(cleaned, ".."+string(filepath.Separator)) || cleaned == ".." {
|
|
return fmt.Errorf("%s path %q must stay within the plugin package", label, path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func capabilityKey(kind, id string) string {
|
|
return kind + "\x00" + id
|
|
}
|
|
|
|
func validateManifestShared(manifest *pluginv1.PluginManifest) error {
|
|
if err := publicmanifest.Validate(manifest); err != nil {
|
|
return err
|
|
}
|
|
if manifest.GetSiloApiVersion() == "" {
|
|
return fmt.Errorf("plugin manifest silo_api_version is required")
|
|
}
|
|
if len(manifest.GetCapabilities()) == 0 {
|
|
return fmt.Errorf("plugin manifest capabilities are required")
|
|
}
|
|
|
|
seenCapabilities := make(map[string]struct{}, len(manifest.GetCapabilities()))
|
|
for _, capability := range manifest.GetCapabilities() {
|
|
key := capabilityKey(capability.GetType(), capability.GetId())
|
|
if _, ok := seenCapabilities[key]; ok {
|
|
return fmt.Errorf("duplicate plugin capability %s", key)
|
|
}
|
|
seenCapabilities[key] = struct{}{}
|
|
}
|
|
|
|
seenAssets := make(map[string]struct{}, len(manifest.GetAssets()))
|
|
for _, asset := range manifest.GetAssets() {
|
|
if err := validateRelativePath(asset.GetPath(), "plugin asset"); err != nil {
|
|
return err
|
|
}
|
|
if _, ok := seenAssets[asset.GetPath()]; ok {
|
|
return fmt.Errorf("duplicate plugin asset %q", asset.GetPath())
|
|
}
|
|
seenAssets[asset.GetPath()] = struct{}{}
|
|
}
|
|
|
|
for _, route := range manifest.GetHttpRoutes() {
|
|
if route == nil {
|
|
continue
|
|
}
|
|
switch route.GetAccess() {
|
|
case "public", "authenticated", "admin":
|
|
default:
|
|
return fmt.Errorf("plugin route %q must declare access as public, authenticated, or admin", route.GetPath())
|
|
}
|
|
if !strings.HasPrefix(route.GetPath(), "/") {
|
|
return fmt.Errorf("plugin route %q must start with /", route.GetPath())
|
|
}
|
|
if route.GetStaticAsset() && route.GetMethod() != "GET" {
|
|
return fmt.Errorf("plugin static asset route %q must use GET", route.GetPath())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateHostShellPageContract(manifest *pluginv1.PluginManifest) error {
|
|
for _, route := range manifest.GetHttpRoutes() {
|
|
if route == nil || !route.GetNavigable() {
|
|
continue
|
|
}
|
|
if route.GetAccess() == "public" {
|
|
return fmt.Errorf("plugin navigable route %q must not declare public access", route.GetPath())
|
|
}
|
|
}
|
|
return nil
|
|
}
|