diff --git a/plugins/README.md b/plugins/README.md index d1d33867b..c11dd2db0 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -126,7 +126,7 @@ Every plugin must include a `manifest.json` file. Example: "permissions": { "http": { "reason": "Fetch metadata from external API", - "allowedHosts": ["api.example.com", "*.musicbrainz.org"] + "requiredHosts": ["api.example.com", "*.musicbrainz.org"] } } } @@ -300,7 +300,7 @@ Make HTTP requests using the Extism PDK's built-in HTTP support. See your [Extis "permissions": { "http": { "reason": "Fetch metadata from external API", - "allowedHosts": ["api.example.com", "*.musicbrainz.org"] + "requiredHosts": ["api.example.com", "*.musicbrainz.org"] } } } @@ -519,7 +519,7 @@ Establish persistent WebSocket connections to external services. "permissions": { "websocket": { "reason": "Real-time connection to service", - "allowedHosts": ["gateway.example.com", "*.discord.gg"] + "requiredHosts": ["gateway.example.com", "*.discord.gg"] } } } diff --git a/plugins/examples/coverartarchive-py/manifest.json b/plugins/examples/coverartarchive-py/manifest.json index a8557b34f..c9a52ba07 100644 --- a/plugins/examples/coverartarchive-py/manifest.json +++ b/plugins/examples/coverartarchive-py/manifest.json @@ -7,7 +7,7 @@ "permissions": { "http": { "reason": "Fetch album cover art from Cover Art Archive API", - "allowedHosts": [ + "requiredHosts": [ "coverartarchive.org", "*.archive.org" ] diff --git a/plugins/examples/crypto-ticker/manifest.json b/plugins/examples/crypto-ticker/manifest.json index 0c51359e1..6fd6ff514 100644 --- a/plugins/examples/crypto-ticker/manifest.json +++ b/plugins/examples/crypto-ticker/manifest.json @@ -13,7 +13,7 @@ }, "websocket": { "reason": "To connect to Coinbase WebSocket API for real-time prices", - "allowedHosts": ["ws-feed.exchange.coinbase.com"] + "requiredHosts": ["ws-feed.exchange.coinbase.com"] } } } diff --git a/plugins/examples/discord-rich-presence-rs/manifest.json b/plugins/examples/discord-rich-presence-rs/manifest.json index feaefd740..f5625815f 100644 --- a/plugins/examples/discord-rich-presence-rs/manifest.json +++ b/plugins/examples/discord-rich-presence-rs/manifest.json @@ -10,11 +10,11 @@ }, "http": { "reason": "To communicate with Discord API for gateway discovery and image uploads", - "allowedHosts": ["discord.com"] + "requiredHosts": ["discord.com"] }, "websocket": { "reason": "To maintain real-time connection with Discord gateway", - "allowedHosts": ["gateway.discord.gg"] + "requiredHosts": ["gateway.discord.gg"] }, "cache": { "reason": "To store connection state and sequence numbers" diff --git a/plugins/examples/discord-rich-presence/manifest.json b/plugins/examples/discord-rich-presence/manifest.json index 41d4a5ec1..006be48e9 100644 --- a/plugins/examples/discord-rich-presence/manifest.json +++ b/plugins/examples/discord-rich-presence/manifest.json @@ -10,13 +10,13 @@ }, "http": { "reason": "To communicate with Discord API for gateway discovery and image uploads", - "allowedHosts": [ + "requiredHosts": [ "discord.com" ] }, "websocket": { "reason": "To maintain real-time connection with Discord gateway", - "allowedHosts": [ + "requiredHosts": [ "gateway.discord.gg" ] }, diff --git a/plugins/examples/webhook-rs/manifest.json b/plugins/examples/webhook-rs/manifest.json index 97686596c..88048a747 100644 --- a/plugins/examples/webhook-rs/manifest.json +++ b/plugins/examples/webhook-rs/manifest.json @@ -7,7 +7,7 @@ "permissions": { "http": { "reason": "To send webhook notifications to configured URLs", - "allowedHosts": ["*"] + "requiredHosts": ["*"] }, "users": { "reason": "Receive scrobble events for users assigned to this plugin" diff --git a/plugins/examples/wikimedia/manifest.json b/plugins/examples/wikimedia/manifest.json index ef84f8718..8590d51a8 100644 --- a/plugins/examples/wikimedia/manifest.json +++ b/plugins/examples/wikimedia/manifest.json @@ -7,7 +7,7 @@ "permissions": { "http": { "reason": "Fetch metadata from Wikimedia APIs", - "allowedHosts": [ + "requiredHosts": [ "query.wikidata.org", "dbpedia.org", "en.wikipedia.org" diff --git a/plugins/host_websocket.go b/plugins/host_websocket.go index 7f946c73d..c4d18c127 100644 --- a/plugins/host_websocket.go +++ b/plugins/host_websocket.go @@ -54,9 +54,9 @@ type wsConnection struct { // webSocketServiceImpl implements host.WebSocketService. // It provides plugins with WebSocket communication capabilities. type webSocketServiceImpl struct { - pluginName string - manager *Manager - allowedHosts []string + pluginName string + manager *Manager + requiredHosts []string mu sync.RWMutex connections map[string]*wsConnection @@ -65,10 +65,10 @@ type webSocketServiceImpl struct { // newWebSocketService creates a new WebSocketService for a plugin. func newWebSocketService(pluginName string, manager *Manager, permission *WebSocketPermission) *webSocketServiceImpl { return &webSocketServiceImpl{ - pluginName: pluginName, - manager: manager, - allowedHosts: permission.AllowedHosts, - connections: make(map[string]*wsConnection), + pluginName: pluginName, + manager: manager, + requiredHosts: permission.RequiredHosts, + connections: make(map[string]*wsConnection), } } @@ -248,7 +248,7 @@ func (s *webSocketServiceImpl) isHostAllowed(host string) bool { hostWithoutPort = host[:idx] } - for _, pattern := range s.allowedHosts { + for _, pattern := range s.requiredHosts { if matchHostPattern(pattern, hostWithoutPort) { return true } diff --git a/plugins/host_websocket_test.go b/plugins/host_websocket_test.go index 0a7091b26..a10cc089f 100644 --- a/plugins/host_websocket_test.go +++ b/plugins/host_websocket_test.go @@ -197,7 +197,7 @@ var _ = Describe("WebSocketService", Ordered, func() { if idx := strings.LastIndex(serverURL, ":"); idx != -1 { hostOnly = serverURL[:idx] } - testService.allowedHosts = append(testService.allowedHosts, hostOnly) + testService.requiredHosts = append(testService.requiredHosts, hostOnly) }) AfterEach(func() { @@ -324,7 +324,7 @@ var _ = Describe("WebSocketService", Ordered, func() { if idx := strings.LastIndex(serverURL, ":"); idx != -1 { hostOnly = serverURL[:idx] } - testService.allowedHosts = append(testService.allowedHosts, hostOnly) + testService.requiredHosts = append(testService.requiredHosts, hostOnly) }) AfterEach(func() { @@ -451,7 +451,7 @@ var _ = Describe("WebSocketService", Ordered, func() { if idx := strings.LastIndex(serverURL, ":"); idx != -1 { hostOnly = serverURL[:idx] } - testService.allowedHosts = append(testService.allowedHosts, hostOnly) + testService.requiredHosts = append(testService.requiredHosts, hostOnly) }) AfterEach(func() { @@ -542,7 +542,7 @@ var _ = Describe("WebSocketService", Ordered, func() { if idx := strings.LastIndex(serverURL, ":"); idx != -1 { hostOnly = serverURL[:idx] } - testService.allowedHosts = append(testService.allowedHosts, hostOnly) + testService.requiredHosts = append(testService.requiredHosts, hostOnly) ctx := GinkgoT().Context() wsURL := "ws://" + serverURL diff --git a/plugins/manager_loader.go b/plugins/manager_loader.go index adc391660..a0ff1a68b 100644 --- a/plugins/manager_loader.go +++ b/plugins/manager_loader.go @@ -268,7 +268,7 @@ func (m *Manager) loadPluginWithConfig(p *model.Plugin) error { Timeout: uint64(defaultTimeout.Milliseconds()), } - if hosts := pkg.Manifest.AllowedHosts(); len(hosts) > 0 { + if hosts := pkg.Manifest.RequiredHTTPHosts(); len(hosts) > 0 { pluginManifest.AllowedHosts = hosts } diff --git a/plugins/manifest-schema.json b/plugins/manifest-schema.json index 2eac3120f..ef141ae7b 100644 --- a/plugins/manifest-schema.json +++ b/plugins/manifest-schema.json @@ -125,9 +125,9 @@ "type": "string", "description": "Explanation for why HTTP access is needed" }, - "allowedHosts": { + "requiredHosts": { "type": "array", - "description": "List of allowed host patterns for HTTP requests (e.g., 'api.example.com', '*.spotify.com')", + "description": "List of required host patterns for HTTP requests (e.g., 'api.example.com', '*.spotify.com')", "items": { "type": "string" } @@ -176,9 +176,9 @@ "type": "string", "description": "Explanation for why WebSocket access is needed" }, - "allowedHosts": { + "requiredHosts": { "type": "array", - "description": "List of allowed host patterns for WebSocket connections (e.g., 'api.example.com', '*.spotify.com')", + "description": "List of required host patterns for WebSocket connections (e.g., 'api.example.com', '*.spotify.com')", "items": { "type": "string" } diff --git a/plugins/manifest.go b/plugins/manifest.go index 9b6a1ad97..7a44000d7 100644 --- a/plugins/manifest.go +++ b/plugins/manifest.go @@ -45,13 +45,13 @@ func ValidateWithCapabilities(m *Manifest, capabilities []Capability) error { return nil } -// AllowedHosts returns a list of allowed hosts for HTTP requests. +// RequiredHTTPHosts returns a list of required hosts for HTTP requests. // Returns the hosts directly from the manifest's permissions. -func (m *Manifest) AllowedHosts() []string { +func (m *Manifest) RequiredHTTPHosts() []string { if m.Permissions == nil || m.Permissions.Http == nil { return nil } - return m.Permissions.Http.AllowedHosts + return m.Permissions.Http.RequiredHosts } // HasExperimentalThreads returns true if the manifest requests experimental threads support. diff --git a/plugins/manifest_gen.go b/plugins/manifest_gen.go index 971e2ffc1..f17127358 100644 --- a/plugins/manifest_gen.go +++ b/plugins/manifest_gen.go @@ -31,12 +31,12 @@ type Experimental struct { // HTTP access permissions for a plugin type HTTPPermission struct { - // List of allowed host patterns for HTTP requests (e.g., 'api.example.com', - // '*.spotify.com') - AllowedHosts []string `json:"allowedHosts,omitempty" yaml:"allowedHosts,omitempty" mapstructure:"allowedHosts,omitempty"` - // Explanation for why HTTP access is needed Reason *string `json:"reason,omitempty" yaml:"reason,omitempty" mapstructure:"reason,omitempty"` + + // List of required host patterns for HTTP requests (e.g., 'api.example.com', + // '*.spotify.com') + RequiredHosts []string `json:"requiredHosts,omitempty" yaml:"requiredHosts,omitempty" mapstructure:"requiredHosts,omitempty"` } // Key-value store permissions for persistent plugin storage @@ -189,10 +189,10 @@ type UsersPermission struct { // WebSocket service permissions for establishing WebSocket connections type WebSocketPermission struct { - // List of allowed host patterns for WebSocket connections (e.g., - // 'api.example.com', '*.spotify.com') - AllowedHosts []string `json:"allowedHosts,omitempty" yaml:"allowedHosts,omitempty" mapstructure:"allowedHosts,omitempty"` - // Explanation for why WebSocket access is needed Reason *string `json:"reason,omitempty" yaml:"reason,omitempty" mapstructure:"reason,omitempty"` + + // List of required host patterns for WebSocket connections (e.g., + // 'api.example.com', '*.spotify.com') + RequiredHosts []string `json:"requiredHosts,omitempty" yaml:"requiredHosts,omitempty" mapstructure:"requiredHosts,omitempty"` } diff --git a/plugins/manifest_test.go b/plugins/manifest_test.go index 636bfe92e..598223049 100644 --- a/plugins/manifest_test.go +++ b/plugins/manifest_test.go @@ -19,7 +19,7 @@ var _ = Describe("Manifest", func() { "permissions": { "http": { "reason": "Fetch metadata", - "allowedHosts": ["api.example.com", "*.spotify.com"] + "requiredHosts": ["api.example.com", "*.spotify.com"] } } }`) @@ -34,7 +34,7 @@ var _ = Describe("Manifest", func() { Expect(*m.Website).To(Equal("https://example.com")) Expect(m.Permissions.Http).ToNot(BeNil()) Expect(*m.Permissions.Http.Reason).To(Equal("Fetch metadata")) - Expect(m.Permissions.Http.AllowedHosts).To(ContainElements("api.example.com", "*.spotify.com")) + Expect(m.Permissions.Http.RequiredHosts).To(ContainElements("api.example.com", "*.spotify.com")) }) It("parses a minimal manifest", func() { @@ -117,11 +117,11 @@ var _ = Describe("Manifest", func() { }) }) - Describe("AllowedHosts", func() { + Describe("RequiredHTTPHosts", func() { It("returns nil when no permissions", func() { m := &Manifest{} - Expect(m.AllowedHosts()).To(BeNil()) + Expect(m.RequiredHTTPHosts()).To(BeNil()) }) It("returns nil when no HTTP permissions", func() { @@ -129,19 +129,19 @@ var _ = Describe("Manifest", func() { Permissions: &Permissions{}, } - Expect(m.AllowedHosts()).To(BeNil()) + Expect(m.RequiredHTTPHosts()).To(BeNil()) }) It("returns hosts from permissions", func() { m := &Manifest{ Permissions: &Permissions{ Http: &HTTPPermission{ - AllowedHosts: []string{"api.example.com", "*.spotify.com"}, + RequiredHosts: []string{"api.example.com", "*.spotify.com"}, }, }, } - hosts := m.AllowedHosts() + hosts := m.RequiredHTTPHosts() Expect(hosts).To(Equal([]string{"api.example.com", "*.spotify.com"})) }) }) diff --git a/plugins/testdata/test-websocket/manifest.json b/plugins/testdata/test-websocket/manifest.json index 2cb2acd86..0ac756470 100644 --- a/plugins/testdata/test-websocket/manifest.json +++ b/plugins/testdata/test-websocket/manifest.json @@ -6,7 +6,7 @@ "permissions": { "websocket": { "reason": "For testing WebSocket callbacks", - "allowedHosts": ["*.example.com", "localhost:*", "echo.websocket.org"] + "requiredHosts": ["*.example.com", "localhost:*", "echo.websocket.org"] } } } diff --git a/resources/i18n/pt-br.json b/resources/i18n/pt-br.json index 53eed88ea..76280c42d 100644 --- a/resources/i18n/pt-br.json +++ b/resources/i18n/pt-br.json @@ -395,7 +395,8 @@ "usersRequired": "Este plugin requer acesso a informações de usuário. Selecione quais usuários o plugin pode acessar, ou habilite 'Permitir todos os usuários'.", "allLibrariesHelp": "Quando habilitado, o plugin terá acesso a todas as bibliotecas, incluindo as criadas no futuro.", "noLibraries": "Nenhuma biblioteca selecionada", - "librariesRequired": "Este plugin requer acesso a informações de bibliotecas. Selecione quais bibliotecas o plugin pode acessar, ou habilite 'Permitir todas as bibliotecas'." + "librariesRequired": "Este plugin requer acesso a informações de bibliotecas. Selecione quais bibliotecas o plugin pode acessar, ou habilite 'Permitir todas as bibliotecas'.", + "requiredHosts": "Hosts necessários" }, "placeholders": { "configKey": "chave", diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index 28f125a8e..2a04b8127 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -395,7 +395,8 @@ "usersRequired": "This plugin requires access to user information. Select which users the plugin can access, or enable 'Allow all users'.", "allLibrariesHelp": "When enabled, the plugin will have access to all libraries, including those created in the future.", "noLibraries": "No libraries selected", - "librariesRequired": "This plugin requires access to library information. Select which libraries the plugin can access, or enable 'Allow all libraries'." + "librariesRequired": "This plugin requires access to library information. Select which libraries the plugin can access, or enable 'Allow all libraries'.", + "requiredHosts": "Required hosts" }, "placeholders": { "configKey": "key", diff --git a/ui/src/plugin/InfoCard.jsx b/ui/src/plugin/InfoCard.jsx index 84c844d27..8fb6853fe 100644 --- a/ui/src/plugin/InfoCard.jsx +++ b/ui/src/plugin/InfoCard.jsx @@ -10,15 +10,17 @@ import { Link, ClickAwayListener, } from '@material-ui/core' +import { useTranslate } from 'react-admin' import { DateField } from '../common' // Helper component for permission chips with clickable persistent tooltips const PermissionChip = ({ label, permission, classes }) => { const [open, setOpen] = useState(false) + const translate = useTranslate() if (!permission) return null - const hasHosts = permission.allowedHosts?.length > 0 + const hasHosts = permission.requiredHosts?.length > 0 const hasTooltip = permission.reason || hasHosts const handleClick = () => { @@ -39,8 +41,8 @@ const PermissionChip = ({ label, permission, classes }) => { {hasHosts && ( - Allowed hosts:{' '} - {permission.allowedHosts.map((host, i) => ( + {translate('resources.plugin.messages.requiredHosts')}:{' '} + {permission.requiredHosts.map((host, i) => ( {i > 0 && ', '} {host}