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 }