github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/api4/plugin.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  // EXPERIMENTAL - SUBJECT TO CHANGE
     5  
     6  package api4
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"io"
    12  	"net/http"
    13  	"net/url"
    14  	"strconv"
    15  
    16  	"github.com/masterhung0112/hk_server/v5/audit"
    17  	"github.com/masterhung0112/hk_server/v5/model"
    18  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    19  	"github.com/masterhung0112/hk_server/v5/store"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  const (
    24  	MaximumPluginFileSize = 50 * 1024 * 1024
    25  )
    26  
    27  func (api *API) InitPlugin() {
    28  	mlog.Debug("EXPERIMENTAL: Initializing plugin api")
    29  
    30  	api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(uploadPlugin)).Methods("POST")
    31  	api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(getPlugins)).Methods("GET")
    32  	api.BaseRoutes.Plugin.Handle("", api.ApiSessionRequired(removePlugin)).Methods("DELETE")
    33  	api.BaseRoutes.Plugins.Handle("/install_from_url", api.ApiSessionRequired(installPluginFromUrl)).Methods("POST")
    34  	api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(installMarketplacePlugin)).Methods("POST")
    35  
    36  	api.BaseRoutes.Plugins.Handle("/statuses", api.ApiSessionRequired(getPluginStatuses)).Methods("GET")
    37  	api.BaseRoutes.Plugin.Handle("/enable", api.ApiSessionRequired(enablePlugin)).Methods("POST")
    38  	api.BaseRoutes.Plugin.Handle("/disable", api.ApiSessionRequired(disablePlugin)).Methods("POST")
    39  
    40  	api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET")
    41  
    42  	api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(getMarketplacePlugins)).Methods("GET")
    43  
    44  	api.BaseRoutes.Plugins.Handle("/marketplace/first_admin_visit", api.ApiHandler(setFirstAdminVisitMarketplaceStatus)).Methods("POST")
    45  	api.BaseRoutes.Plugins.Handle("/marketplace/first_admin_visit", api.ApiSessionRequired(getFirstAdminVisitMarketplaceStatus)).Methods("GET")
    46  }
    47  
    48  func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
    49  	config := c.App.Config()
    50  	if !*config.PluginSettings.Enable || !*config.PluginSettings.EnableUploads || *config.PluginSettings.RequirePluginSignature {
    51  		c.Err = model.NewAppError("uploadPlugin", "app.plugin.upload_disabled.app_error", nil, "", http.StatusNotImplemented)
    52  		return
    53  	}
    54  
    55  	auditRec := c.MakeAuditRecord("uploadPlugin", audit.Fail)
    56  	defer c.LogAuditRec(auditRec)
    57  
    58  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
    59  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
    60  		return
    61  	}
    62  
    63  	if err := r.ParseMultipartForm(MaximumPluginFileSize); err != nil {
    64  		http.Error(w, err.Error(), http.StatusBadRequest)
    65  		return
    66  	}
    67  
    68  	m := r.MultipartForm
    69  
    70  	pluginArray, ok := m.File["plugin"]
    71  	if !ok {
    72  		c.Err = model.NewAppError("uploadPlugin", "api.plugin.upload.no_file.app_error", nil, "", http.StatusBadRequest)
    73  		return
    74  	}
    75  
    76  	if len(pluginArray) <= 0 {
    77  		c.Err = model.NewAppError("uploadPlugin", "api.plugin.upload.array.app_error", nil, "", http.StatusBadRequest)
    78  		return
    79  	}
    80  	auditRec.AddMeta("filename", pluginArray[0].Filename)
    81  
    82  	file, err := pluginArray[0].Open()
    83  	if err != nil {
    84  		c.Err = model.NewAppError("uploadPlugin", "api.plugin.upload.file.app_error", nil, "", http.StatusBadRequest)
    85  		return
    86  	}
    87  	defer file.Close()
    88  
    89  	force := false
    90  	if len(m.Value["force"]) > 0 && m.Value["force"][0] == "true" {
    91  		force = true
    92  	}
    93  
    94  	installPlugin(c, w, file, force)
    95  	auditRec.Success()
    96  }
    97  
    98  func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) {
    99  	if !*c.App.Config().PluginSettings.Enable ||
   100  		*c.App.Config().PluginSettings.RequirePluginSignature ||
   101  		!*c.App.Config().PluginSettings.EnableUploads {
   102  		c.Err = model.NewAppError("installPluginFromUrl", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   103  		return
   104  	}
   105  
   106  	auditRec := c.MakeAuditRecord("installPluginFromUrl", audit.Fail)
   107  	defer c.LogAuditRec(auditRec)
   108  
   109  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
   110  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
   111  		return
   112  	}
   113  
   114  	force, _ := strconv.ParseBool(r.URL.Query().Get("force"))
   115  	downloadURL := r.URL.Query().Get("plugin_download_url")
   116  	auditRec.AddMeta("url", downloadURL)
   117  
   118  	pluginFileBytes, err := c.App.DownloadFromURL(downloadURL)
   119  	if err != nil {
   120  		c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.download_failed.app_error", nil, err.Error(), http.StatusBadRequest)
   121  		return
   122  	}
   123  
   124  	installPlugin(c, w, bytes.NewReader(pluginFileBytes), force)
   125  	auditRec.Success()
   126  }
   127  
   128  func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
   129  	if !*c.App.Config().PluginSettings.Enable {
   130  		c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   131  		return
   132  	}
   133  
   134  	if !*c.App.Config().PluginSettings.EnableMarketplace {
   135  		c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.marketplace_disabled.app_error", nil, "", http.StatusNotImplemented)
   136  		return
   137  	}
   138  
   139  	auditRec := c.MakeAuditRecord("installMarketplacePlugin", audit.Fail)
   140  	defer c.LogAuditRec(auditRec)
   141  
   142  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
   143  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
   144  		return
   145  	}
   146  
   147  	pluginRequest, err := model.PluginRequestFromReader(r.Body)
   148  	if err != nil {
   149  		c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.marketplace_plugin_request.app_error", nil, err.Error(), http.StatusNotImplemented)
   150  		return
   151  	}
   152  	auditRec.AddMeta("plugin_id", pluginRequest.Id)
   153  
   154  	manifest, appErr := c.App.InstallMarketplacePlugin(pluginRequest)
   155  	if appErr != nil {
   156  		c.Err = appErr
   157  		return
   158  	}
   159  
   160  	auditRec.Success()
   161  	auditRec.AddMeta("plugin_name", manifest.Name)
   162  	auditRec.AddMeta("plugin_desc", manifest.Description)
   163  
   164  	w.WriteHeader(http.StatusCreated)
   165  	w.Write([]byte(manifest.ToJson()))
   166  }
   167  
   168  func getPlugins(c *Context, w http.ResponseWriter, r *http.Request) {
   169  	if !*c.App.Config().PluginSettings.Enable {
   170  		c.Err = model.NewAppError("getPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   171  		return
   172  	}
   173  
   174  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_READ_PLUGINS) {
   175  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_PLUGINS)
   176  		return
   177  	}
   178  
   179  	response, err := c.App.GetPlugins()
   180  	if err != nil {
   181  		c.Err = err
   182  		return
   183  	}
   184  
   185  	w.Write([]byte(response.ToJson()))
   186  }
   187  
   188  func getPluginStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
   189  	if !*c.App.Config().PluginSettings.Enable {
   190  		c.Err = model.NewAppError("getPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   191  		return
   192  	}
   193  
   194  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_READ_PLUGINS) {
   195  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_PLUGINS)
   196  		return
   197  	}
   198  
   199  	response, err := c.App.GetClusterPluginStatuses()
   200  	if err != nil {
   201  		c.Err = err
   202  		return
   203  	}
   204  
   205  	w.Write([]byte(response.ToJson()))
   206  }
   207  
   208  func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
   209  	c.RequirePluginId()
   210  	if c.Err != nil {
   211  		return
   212  	}
   213  
   214  	if !*c.App.Config().PluginSettings.Enable {
   215  		c.Err = model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   216  		return
   217  	}
   218  
   219  	auditRec := c.MakeAuditRecord("removePlugin", audit.Fail)
   220  	defer c.LogAuditRec(auditRec)
   221  	auditRec.AddMeta("plugin_id", c.Params.PluginId)
   222  
   223  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
   224  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
   225  		return
   226  	}
   227  
   228  	err := c.App.RemovePlugin(c.Params.PluginId)
   229  	if err != nil {
   230  		c.Err = err
   231  		return
   232  	}
   233  
   234  	auditRec.Success()
   235  	ReturnStatusOK(w)
   236  }
   237  
   238  func getWebappPlugins(c *Context, w http.ResponseWriter, r *http.Request) {
   239  	if !*c.App.Config().PluginSettings.Enable {
   240  		c.Err = model.NewAppError("getWebappPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   241  		return
   242  	}
   243  
   244  	manifests, err := c.App.GetActivePluginManifests()
   245  	if err != nil {
   246  		c.Err = err
   247  		return
   248  	}
   249  
   250  	clientManifests := []*model.Manifest{}
   251  	for _, m := range manifests {
   252  		if m.HasClient() {
   253  			manifest := m.ClientManifest()
   254  
   255  			// There is no reason to expose the SettingsSchema in this API call; it's not used in the webapp.
   256  			manifest.SettingsSchema = nil
   257  			clientManifests = append(clientManifests, manifest)
   258  		}
   259  	}
   260  
   261  	w.Write([]byte(model.ManifestListToJson(clientManifests)))
   262  }
   263  
   264  func getMarketplacePlugins(c *Context, w http.ResponseWriter, r *http.Request) {
   265  	if !*c.App.Config().PluginSettings.Enable {
   266  		c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   267  		return
   268  	}
   269  
   270  	if !*c.App.Config().PluginSettings.EnableMarketplace {
   271  		c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marketplace_disabled.app_error", nil, "", http.StatusNotImplemented)
   272  		return
   273  	}
   274  
   275  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_READ_PLUGINS) {
   276  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_PLUGINS)
   277  		return
   278  	}
   279  
   280  	filter, err := parseMarketplacePluginFilter(r.URL)
   281  	if err != nil {
   282  		c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
   283  		return
   284  	}
   285  
   286  	plugins, appErr := c.App.GetMarketplacePlugins(filter)
   287  	if appErr != nil {
   288  		c.Err = appErr
   289  		return
   290  	}
   291  
   292  	json, err := json.Marshal(plugins)
   293  	if err != nil {
   294  		c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
   295  		return
   296  	}
   297  
   298  	w.Write(json)
   299  }
   300  
   301  func enablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
   302  	c.RequirePluginId()
   303  	if c.Err != nil {
   304  		return
   305  	}
   306  
   307  	if !*c.App.Config().PluginSettings.Enable {
   308  		c.Err = model.NewAppError("activatePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   309  		return
   310  	}
   311  
   312  	auditRec := c.MakeAuditRecord("enablePlugin", audit.Fail)
   313  	defer c.LogAuditRec(auditRec)
   314  	auditRec.AddMeta("plugin_id", c.Params.PluginId)
   315  
   316  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
   317  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
   318  		return
   319  	}
   320  
   321  	if err := c.App.EnablePlugin(c.Params.PluginId); err != nil {
   322  		c.Err = err
   323  		return
   324  	}
   325  
   326  	auditRec.Success()
   327  	ReturnStatusOK(w)
   328  }
   329  
   330  func disablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
   331  	c.RequirePluginId()
   332  	if c.Err != nil {
   333  		return
   334  	}
   335  
   336  	if !*c.App.Config().PluginSettings.Enable {
   337  		c.Err = model.NewAppError("deactivatePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   338  		return
   339  	}
   340  
   341  	auditRec := c.MakeAuditRecord("disablePlugin", audit.Fail)
   342  	defer c.LogAuditRec(auditRec)
   343  	auditRec.AddMeta("plugin_id", c.Params.PluginId)
   344  
   345  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS) {
   346  		c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_PLUGINS)
   347  		return
   348  	}
   349  
   350  	if err := c.App.DisablePlugin(c.Params.PluginId); err != nil {
   351  		c.Err = err
   352  		return
   353  	}
   354  
   355  	auditRec.Success()
   356  	ReturnStatusOK(w)
   357  }
   358  
   359  func parseMarketplacePluginFilter(u *url.URL) (*model.MarketplacePluginFilter, error) {
   360  	page, err := parseInt(u, "page", 0)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	perPage, err := parseInt(u, "per_page", 100)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	filter := u.Query().Get("filter")
   371  	serverVersion := u.Query().Get("server_version")
   372  	localOnly, _ := strconv.ParseBool(u.Query().Get("local_only"))
   373  	return &model.MarketplacePluginFilter{
   374  		Page:          page,
   375  		PerPage:       perPage,
   376  		Filter:        filter,
   377  		ServerVersion: serverVersion,
   378  		LocalOnly:     localOnly,
   379  	}, nil
   380  }
   381  
   382  func installPlugin(c *Context, w http.ResponseWriter, plugin io.ReadSeeker, force bool) {
   383  	manifest, appErr := c.App.InstallPlugin(plugin, force)
   384  	if appErr != nil {
   385  		c.Err = appErr
   386  		return
   387  	}
   388  	w.WriteHeader(http.StatusCreated)
   389  	w.Write([]byte(manifest.ToJson()))
   390  }
   391  
   392  func setFirstAdminVisitMarketplaceStatus(c *Context, w http.ResponseWriter, r *http.Request) {
   393  	auditRec := c.MakeAuditRecord("setFirstAdminVisitMarketplaceStatus", audit.Fail)
   394  	defer c.LogAuditRec(auditRec)
   395  	c.LogAudit("attempt")
   396  
   397  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_MANAGE_SYSTEM) {
   398  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   399  		return
   400  	}
   401  
   402  	firstAdminVisitMarketplaceObj := model.System{
   403  		Name:  model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE,
   404  		Value: "true",
   405  	}
   406  
   407  	if err := c.App.Srv().Store.System().SaveOrUpdate(&firstAdminVisitMarketplaceObj); err != nil {
   408  		c.Err = model.NewAppError("setFirstAdminVisitMarketplaceStatus", "api.error_set_first_admin_visit_marketplace_status", nil, err.Error(), http.StatusInternalServerError)
   409  		return
   410  	}
   411  
   412  	message := model.NewWebSocketEvent(model.WEBSOCKET_FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED, "", "", "", nil)
   413  	message.Add("firstAdminVisitMarketplaceStatus", firstAdminVisitMarketplaceObj.Value)
   414  	c.App.Publish(message)
   415  
   416  	auditRec.Success()
   417  	ReturnStatusOK(w)
   418  }
   419  
   420  func getFirstAdminVisitMarketplaceStatus(c *Context, w http.ResponseWriter, r *http.Request) {
   421  	auditRec := c.MakeAuditRecord("getFirstAdminVisitMarketplaceStatus", audit.Fail)
   422  	defer c.LogAuditRec(auditRec)
   423  	c.LogAudit("attempt")
   424  
   425  	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_MANAGE_SYSTEM) {
   426  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   427  		return
   428  	}
   429  
   430  	firstAdminVisitMarketplaceObj, err := c.App.Srv().Store.System().GetByName(model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE)
   431  	if err != nil {
   432  		var nfErr *store.ErrNotFound
   433  		switch {
   434  		case errors.As(err, &nfErr):
   435  			firstAdminVisitMarketplaceObj = &model.System{
   436  				Name:  model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE,
   437  				Value: "false",
   438  			}
   439  		default:
   440  			c.Err = model.NewAppError("getFirstAdminVisitMarketplaceStatus", "api.error_get_first_admin_visit_marketplace_status", nil, err.Error(), http.StatusInternalServerError)
   441  
   442  			return
   443  		}
   444  	}
   445  
   446  	auditRec.Success()
   447  	w.Write([]byte(firstAdminVisitMarketplaceObj.ToJson()))
   448  }