mirror of
https://github.com/navidrome/navidrome.git
synced 2025-08-10 00:52:20 +00:00
feat(plugins): allow Plugins to call the Subsonic API (#4260)
* chore: .gitignore any navidrome binary Signed-off-by: Deluan <deluan@navidrome.org> * feat: implement internal authentication handling in middleware Signed-off-by: Deluan <deluan@navidrome.org> * feat(manager): add SubsonicRouter to Manager for API routing Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): add SubsonicAPI Host service for plugins and an example plugin Signed-off-by: Deluan <deluan@navidrome.org> * fix lint Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): refactor path handling in SubsonicAPI to extract endpoint correctly Signed-off-by: Deluan <deluan@navidrome.org> * docs(plugins): add SubsonicAPI service documentation to README Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): implement permission checks for SubsonicAPI service Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): enhance SubsonicAPI service initialization with atomic router handling Signed-off-by: Deluan <deluan@navidrome.org> * refactor(plugins): better encapsulated dependency injection Signed-off-by: Deluan <deluan@navidrome.org> * refactor(plugins): rename parameter in WithInternalAuth for clarity Signed-off-by: Deluan <deluan@navidrome.org> * docs(plugins): update SubsonicAPI permissions section in README for clarity and detail Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): enhance SubsonicAPI permissions output with allowed usernames and admin flag Signed-off-by: Deluan <deluan@navidrome.org> * feat(plugins): add schema reference to example plugins Signed-off-by: Deluan <deluan@navidrome.org> * remove import alias Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -157,6 +157,27 @@
|
||||
"description": "Artwork service permissions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subsonicapi": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/$defs/basePermission" },
|
||||
{
|
||||
"type": "object",
|
||||
"description": "SubsonicAPI service permissions",
|
||||
"properties": {
|
||||
"allowedUsernames": {
|
||||
"type": "array",
|
||||
"description": "List of usernames the plugin can pass as u. Any user if empty",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"allowAdmins": {
|
||||
"type": "boolean",
|
||||
"description": "If false, reject calls where the u is an admin",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,9 @@ type PluginManifestPermissions struct {
|
||||
// Scheduler corresponds to the JSON schema field "scheduler".
|
||||
Scheduler *PluginManifestPermissionsScheduler `json:"scheduler,omitempty" yaml:"scheduler,omitempty" mapstructure:"scheduler,omitempty"`
|
||||
|
||||
// Subsonicapi corresponds to the JSON schema field "subsonicapi".
|
||||
Subsonicapi *PluginManifestPermissionsSubsonicapi `json:"subsonicapi,omitempty" yaml:"subsonicapi,omitempty" mapstructure:"subsonicapi,omitempty"`
|
||||
|
||||
// Websocket corresponds to the JSON schema field "websocket".
|
||||
Websocket *PluginManifestPermissionsWebsocket `json:"websocket,omitempty" yaml:"websocket,omitempty" mapstructure:"websocket,omitempty"`
|
||||
|
||||
@@ -305,6 +308,42 @@ func (j *PluginManifestPermissionsScheduler) UnmarshalJSON(value []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubsonicAPI service permissions
|
||||
type PluginManifestPermissionsSubsonicapi struct {
|
||||
// If false, reject calls where the u is an admin
|
||||
AllowAdmins bool `json:"allowAdmins,omitempty" yaml:"allowAdmins,omitempty" mapstructure:"allowAdmins,omitempty"`
|
||||
|
||||
// List of usernames the plugin can pass as u. Any user if empty
|
||||
AllowedUsernames []string `json:"allowedUsernames,omitempty" yaml:"allowedUsernames,omitempty" mapstructure:"allowedUsernames,omitempty"`
|
||||
|
||||
// Explanation of why this permission is needed
|
||||
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *PluginManifestPermissionsSubsonicapi) UnmarshalJSON(value []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(value, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := raw["reason"]; raw != nil && !ok {
|
||||
return fmt.Errorf("field reason in PluginManifestPermissionsSubsonicapi: required")
|
||||
}
|
||||
type Plain PluginManifestPermissionsSubsonicapi
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(value, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["allowAdmins"]; !ok || v == nil {
|
||||
plain.AllowAdmins = false
|
||||
}
|
||||
if len(plain.Reason) < 1 {
|
||||
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
|
||||
}
|
||||
*j = PluginManifestPermissionsSubsonicapi(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebSocket service permissions
|
||||
type PluginManifestPermissionsWebsocket struct {
|
||||
// Whether to allow connections to local/private network addresses
|
||||
|
||||
Reference in New Issue
Block a user