417 lines
11 KiB
Go
417 lines
11 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gotify/location"
|
|
"github.com/gotify/server/v2/auth"
|
|
"github.com/gotify/server/v2/model"
|
|
"github.com/gotify/server/v2/plugin"
|
|
"github.com/gotify/server/v2/plugin/compat"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// The PluginDatabase interface for encapsulating database access.
|
|
type PluginDatabase interface {
|
|
GetPluginConfByUser(userid uint) ([]*model.PluginConf, error)
|
|
UpdatePluginConf(p *model.PluginConf) error
|
|
GetPluginConfByID(id uint) (*model.PluginConf, error)
|
|
}
|
|
|
|
// The PluginAPI provides handlers for managing plugins.
|
|
type PluginAPI struct {
|
|
Notifier Notifier
|
|
Manager *plugin.Manager
|
|
DB PluginDatabase
|
|
}
|
|
|
|
// GetPlugins returns all plugins a user has.
|
|
// swagger:operation GET /plugin plugin getPlugins
|
|
//
|
|
// Return all plugins.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// type: array
|
|
// items:
|
|
// $ref: "#/definitions/PluginConf"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) GetPlugins(ctx *gin.Context) {
|
|
userID := auth.GetUserID(ctx)
|
|
plugins, err := c.DB.GetPluginConfByUser(userID)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
result := make([]model.PluginConfExternal, 0)
|
|
for _, conf := range plugins {
|
|
if inst, err := c.Manager.Instance(conf.ID); err == nil {
|
|
info := c.Manager.PluginInfo(conf.ModulePath)
|
|
result = append(result, model.PluginConfExternal{
|
|
ID: conf.ID,
|
|
Name: info.String(),
|
|
Token: conf.Token,
|
|
ModulePath: conf.ModulePath,
|
|
Author: info.Author,
|
|
Website: info.Website,
|
|
License: info.License,
|
|
Enabled: conf.Enabled,
|
|
Capabilities: inst.Supports().Strings(),
|
|
})
|
|
}
|
|
}
|
|
ctx.JSON(200, result)
|
|
}
|
|
|
|
// EnablePlugin enables a plugin.
|
|
// swagger:operation POST /plugin/{id}/enable plugin enablePlugin
|
|
//
|
|
// Enable a plugin.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the plugin id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) EnablePlugin(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
conf, err := c.DB.GetPluginConfByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if conf == nil || !isPluginOwner(ctx, conf) {
|
|
ctx.AbortWithError(404, errors.New("unknown plugin"))
|
|
return
|
|
}
|
|
_, err = c.Manager.Instance(id)
|
|
if err != nil {
|
|
ctx.AbortWithError(404, errors.New("plugin instance not found"))
|
|
return
|
|
}
|
|
if err := c.Manager.SetPluginEnabled(id, true); err == plugin.ErrAlreadyEnabledOrDisabled {
|
|
ctx.AbortWithError(400, err)
|
|
} else if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// DisablePlugin disables a plugin.
|
|
// swagger:operation POST /plugin/{id}/disable plugin disablePlugin
|
|
//
|
|
// Disable a plugin.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the plugin id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) DisablePlugin(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
conf, err := c.DB.GetPluginConfByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if conf == nil || !isPluginOwner(ctx, conf) {
|
|
ctx.AbortWithError(404, errors.New("unknown plugin"))
|
|
return
|
|
}
|
|
_, err = c.Manager.Instance(id)
|
|
if err != nil {
|
|
ctx.AbortWithError(404, errors.New("plugin instance not found"))
|
|
return
|
|
}
|
|
if err := c.Manager.SetPluginEnabled(id, false); err == plugin.ErrAlreadyEnabledOrDisabled {
|
|
ctx.AbortWithError(400, err)
|
|
} else if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// GetDisplay get display info for Displayer plugin.
|
|
// swagger:operation GET /plugin/{id}/display plugin getPluginDisplay
|
|
//
|
|
// Get display info for a Displayer plugin.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the plugin id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// type: string
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) GetDisplay(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
conf, err := c.DB.GetPluginConfByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if conf == nil || !isPluginOwner(ctx, conf) {
|
|
ctx.AbortWithError(404, errors.New("unknown plugin"))
|
|
return
|
|
}
|
|
instance, err := c.Manager.Instance(id)
|
|
if err != nil {
|
|
ctx.AbortWithError(404, errors.New("plugin instance not found"))
|
|
return
|
|
}
|
|
ctx.JSON(200, instance.GetDisplay(location.Get(ctx)))
|
|
})
|
|
}
|
|
|
|
// GetConfig returns Configurer plugin configuration in YAML format.
|
|
// swagger:operation GET /plugin/{id}/config plugin getPluginConfig
|
|
//
|
|
// Get YAML configuration for Configurer plugin.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/x-yaml]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the plugin id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// type: object
|
|
// description: plugin configuration
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) GetConfig(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
conf, err := c.DB.GetPluginConfByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if conf == nil || !isPluginOwner(ctx, conf) {
|
|
ctx.AbortWithError(404, errors.New("unknown plugin"))
|
|
return
|
|
}
|
|
instance, err := c.Manager.Instance(id)
|
|
if err != nil {
|
|
ctx.AbortWithError(404, errors.New("plugin instance not found"))
|
|
return
|
|
}
|
|
|
|
if aborted := supportOrAbort(ctx, instance, compat.Configurer); aborted {
|
|
return
|
|
}
|
|
|
|
ctx.Header("content-type", "application/x-yaml")
|
|
ctx.Writer.Write(conf.Config)
|
|
})
|
|
}
|
|
|
|
// UpdateConfig updates Configurer plugin configuration in YAML format.
|
|
// swagger:operation POST /plugin/{id}/config plugin updatePluginConfig
|
|
//
|
|
// Update YAML configuration for Configurer plugin.
|
|
//
|
|
// ---
|
|
// consumes: [application/x-yaml]
|
|
// produces: [application/json]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the plugin id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 500:
|
|
// description: Internal Server Error
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (c *PluginAPI) UpdateConfig(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
conf, err := c.DB.GetPluginConfByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if conf == nil || !isPluginOwner(ctx, conf) {
|
|
ctx.AbortWithError(404, errors.New("unknown plugin"))
|
|
return
|
|
}
|
|
instance, err := c.Manager.Instance(id)
|
|
if err != nil {
|
|
ctx.AbortWithError(404, errors.New("plugin instance not found"))
|
|
return
|
|
}
|
|
|
|
if aborted := supportOrAbort(ctx, instance, compat.Configurer); aborted {
|
|
return
|
|
}
|
|
|
|
newConf := instance.DefaultConfig()
|
|
newconfBytes, err := io.ReadAll(ctx.Request.Body)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
if err := yaml.Unmarshal(newconfBytes, newConf); err != nil {
|
|
ctx.AbortWithError(400, err)
|
|
return
|
|
}
|
|
if err := instance.ValidateAndSetConfig(newConf); err != nil {
|
|
ctx.AbortWithError(400, err)
|
|
return
|
|
}
|
|
conf.Config = newconfBytes
|
|
successOrAbort(ctx, 500, c.DB.UpdatePluginConf(conf))
|
|
})
|
|
}
|
|
|
|
func isPluginOwner(ctx *gin.Context, conf *model.PluginConf) bool {
|
|
return conf.UserID == auth.GetUserID(ctx)
|
|
}
|
|
|
|
func supportOrAbort(ctx *gin.Context, instance compat.PluginInstance, module compat.Capability) (aborted bool) {
|
|
if compat.HasSupport(instance, module) {
|
|
return false
|
|
}
|
|
ctx.AbortWithError(400, fmt.Errorf("plugin does not support %s", module))
|
|
return true
|
|
}
|