Files
mostlymatter/api4/system.go
Agniva De Sarker ab8de49f0a MM-40818: Refactor product initialization to happen late (#19658)
This is more of a general refactor of the initialization
process which should allow us to pass services more
easily.

The changes are minimal to keep the scope limited.
For now, the objective is to pass the file service
to the Channels product. For that, it was required
to move some of the enterprise interfaces under Channels
from Server.

We also create a filestore field in the server to
avoid creating filestore reference every time
we make a filestore operation. This will be later
passed on to the Channels product.

Also removed an unnecessary test. 
The test was working so far because we were creating
the filebackend every time for every request. But
we should go via UpdateConfig call which would fail,
were we to assign an invalid filestore name.

So we were actually testing for a different thing.
Therefore, removed the test.

```release-note
NONE
```
2022-03-03 12:22:10 +05:30

933 строки
29 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"path"
"runtime"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v6/audit"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/services/cache"
"github.com/mattermost/mattermost-server/v6/services/upgrader"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
const (
RedirectLocationCacheSize = 10000
DefaultServerBusySeconds = 3600
MaxServerBusySeconds = 86400
)
var redirectLocationDataCache = cache.NewLRU(cache.LRUOptions{
Size: RedirectLocationCacheSize,
})
func (api *API) InitSystem() {
api.BaseRoutes.System.Handle("/ping", api.APIHandler(getSystemPing)).Methods("GET")
api.BaseRoutes.System.Handle("/timezones", api.APISessionRequired(getSupportedTimezones)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/audits", api.APISessionRequired(getAudits)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/email/test", api.APISessionRequired(testEmail)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/site_url/test", api.APISessionRequired(testSiteURL)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/file/s3_test", api.APISessionRequired(testS3)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/database/recycle", api.APISessionRequired(databaseRecycle)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/caches/invalidate", api.APISessionRequired(invalidateCaches)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/logs", api.APISessionRequired(getLogs)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/logs", api.APIHandler(postLog)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/analytics/old", api.APISessionRequired(getAnalytics)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/latest_version", api.APISessionRequired(getLatestVersion)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/redirect_location", api.APISessionRequiredTrustRequester(getRedirectLocation)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/notifications/ack", api.APISessionRequired(pushNotificationAck)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/server_busy", api.APISessionRequired(setServerBusy)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/server_busy", api.APISessionRequired(getServerBusyExpires)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/server_busy", api.APISessionRequired(clearServerBusy)).Methods("DELETE")
api.BaseRoutes.APIRoot.Handle("/upgrade_to_enterprise", api.APISessionRequired(upgradeToEnterprise)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/upgrade_to_enterprise/status", api.APISessionRequired(upgradeToEnterpriseStatus)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/restart", api.APISessionRequired(restart)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/status", api.APISessionRequired(getWarnMetricsStatus)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.APIHandler(sendWarnMetricAckEmail)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/trial-license-ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.APIHandler(requestTrialLicenseAndAckWarnMetric)).Methods("POST")
api.BaseRoutes.System.Handle("/notices/{team_id:[A-Za-z0-9]+}", api.APISessionRequired(getProductNotices)).Methods("GET")
api.BaseRoutes.System.Handle("/notices/view", api.APISessionRequired(updateViewedProductNotices)).Methods("PUT")
api.BaseRoutes.System.Handle("/support_packet", api.APISessionRequired(generateSupportPacket)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(getOnboarding)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(completeOnboarding)).Methods("POST")
}
func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
const FileMime = "application/zip"
const OutputDirectory = "support_packet"
// Checking to see if the user is a admin of any sort or not
// If they are a admin, they should theoretically have access to one or more of the system console read permissions
if !c.App.SessionHasPermissionToAny(*c.AppContext.Session(), model.SysconsoleReadPermissions) {
c.SetPermissionError(model.SysconsoleReadPermissions...)
return
}
// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
if c.App.Srv().License() == nil {
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
return
}
fileDatas := c.App.GenerateSupportPacket()
// Constructing the ZIP file name as per spec (mattermost_support_packet_YYYY-MM-DD-HH-MM.zip)
now := time.Now()
outputZipFilename := fmt.Sprintf("mattermost_support_packet_%s.zip", now.Format("2006-01-02-03-04"))
fileStorageBackend := c.App.FileBackend()
// We do this incase we get concurrent requests, we will always have a unique directory.
// This is to avoid the situation where we try to write to the same directory while we are trying to delete it (further down)
outputDirectoryToUse := OutputDirectory + "_" + model.NewId()
err := c.App.CreateZipFileAndAddFiles(fileStorageBackend, fileDatas, outputZipFilename, outputDirectoryToUse)
if err != nil {
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_create_zip_file", nil, err.Error(), http.StatusForbidden)
return
}
fileBytes, err := fileStorageBackend.ReadFile(path.Join(outputDirectoryToUse, outputZipFilename))
defer fileStorageBackend.RemoveDirectory(outputDirectoryToUse)
if err != nil {
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_read_file_from_backend", nil, err.Error(), http.StatusForbidden)
return
}
fileBytesReader := bytes.NewReader(fileBytes)
// Send the zip file back to client
// We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go)
// already sets that for us
writeFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r)
}
func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
reqs := c.App.Config().ClientRequirements
s := make(map[string]string)
s[model.STATUS] = model.StatusOk
s["AndroidLatestVersion"] = reqs.AndroidLatestVersion
s["AndroidMinVersion"] = reqs.AndroidMinVersion
s["IosLatestVersion"] = reqs.IosLatestVersion
s["IosMinVersion"] = reqs.IosMinVersion
testflag := c.App.Config().FeatureFlags.TestFeature
if testflag != "off" {
s["TestFeatureFlag"] = testflag
}
actualGoroutines := runtime.NumGoroutine()
if *c.App.Config().ServiceSettings.GoroutineHealthThreshold > 0 && actualGoroutines >= *c.App.Config().ServiceSettings.GoroutineHealthThreshold {
mlog.Warn("The number of running goroutines is over the health threshold", mlog.Int("goroutines", actualGoroutines), mlog.Int("health_threshold", *c.App.Config().ServiceSettings.GoroutineHealthThreshold))
s[model.STATUS] = model.StatusUnhealthy
}
// Enhanced ping health check:
// If an extra form value is provided then perform extra health checks for
// database and file storage backends.
if r.FormValue("get_server_status") != "" {
dbStatusKey := "database_status"
s[dbStatusKey] = model.StatusOk
writeErr := c.App.DBHealthCheckWrite()
if writeErr != nil {
mlog.Warn("Unable to write to database.", mlog.Err(writeErr))
s[dbStatusKey] = model.StatusUnhealthy
s[model.STATUS] = model.StatusUnhealthy
}
writeErr = c.App.DBHealthCheckDelete()
if writeErr != nil {
mlog.Warn("Unable to remove ping health check value from database.", mlog.Err(writeErr))
s[dbStatusKey] = model.StatusUnhealthy
s[model.STATUS] = model.StatusUnhealthy
}
if s[dbStatusKey] == model.StatusOk {
mlog.Debug("Able to write to database.")
}
filestoreStatusKey := "filestore_status"
s[filestoreStatusKey] = model.StatusOk
appErr := c.App.TestFileStoreConnection()
if appErr != nil {
s[filestoreStatusKey] = model.StatusUnhealthy
s[model.STATUS] = model.StatusUnhealthy
}
w.Header().Set(model.STATUS, s[model.STATUS])
w.Header().Set(dbStatusKey, s[dbStatusKey])
w.Header().Set(filestoreStatusKey, s[filestoreStatusKey])
}
if deviceID := r.FormValue("device_id"); deviceID != "" {
s["CanReceiveNotifications"] = c.App.SendTestPushNotification(deviceID)
}
if s[model.STATUS] != model.StatusOk {
w.WriteHeader(http.StatusInternalServerError)
}
w.Write([]byte(model.MapToJSON(s)))
}
func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
cfg := model.ConfigFromJSON(r.Body)
if cfg == nil {
cfg = c.App.Config()
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionTestEmail) {
c.SetPermissionError(model.PermissionTestEmail)
return
}
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("testEmail", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
err := c.App.TestEmail(c.AppContext.Session().UserId, cfg)
if err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}
func testSiteURL(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionTestSiteURL) {
c.SetPermissionError(model.PermissionTestSiteURL)
return
}
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("testSiteURL", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
props := model.MapFromJSON(r.Body)
siteURL := props["site_url"]
if siteURL == "" {
c.SetInvalidParam("site_url")
return
}
err := c.App.TestSiteURL(siteURL)
if err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}
func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("getAudits", audit.Fail)
defer c.LogAuditRec(auditRec)
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadAudits) {
c.SetPermissionError(model.PermissionReadAudits)
return
}
audits, err := c.App.GetAuditsPage("", c.Params.Page, c.Params.PerPage)
if err != nil {
c.Err = err
return
}
auditRec.Success()
auditRec.AddMeta("page", c.Params.Page)
auditRec.AddMeta("audits_per_page", c.Params.LogsPerPage)
if err := json.NewEncoder(w).Encode(audits); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err))
}
}
func databaseRecycle(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionRecycleDatabaseConnections) {
c.SetPermissionError(model.PermissionRecycleDatabaseConnections)
return
}
auditRec := c.MakeAuditRecord("databaseRecycle", audit.Fail)
defer c.LogAuditRec(auditRec)
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("databaseRecycle", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
c.App.RecycleDatabaseConnection()
auditRec.Success()
ReturnStatusOK(w)
}
func invalidateCaches(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionInvalidateCaches) {
c.SetPermissionError(model.PermissionInvalidateCaches)
return
}
auditRec := c.MakeAuditRecord("invalidateCaches", audit.Fail)
defer c.LogAuditRec(auditRec)
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("invalidateCaches", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
err := c.App.Srv().InvalidateAllCaches()
if err != nil {
c.Err = err
return
}
auditRec.Success()
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
ReturnStatusOK(w)
}
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("getLogs", audit.Fail)
defer c.LogAuditRec(auditRec)
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("getLogs", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionGetLogs) {
c.SetPermissionError(model.PermissionGetLogs)
return
}
lines, err := c.App.GetLogs(c.Params.Page, c.Params.LogsPerPage)
if err != nil {
c.Err = err
return
}
auditRec.AddMeta("page", c.Params.Page)
auditRec.AddMeta("logs_per_page", c.Params.LogsPerPage)
w.Write([]byte(model.ArrayToJSON(lines)))
}
func postLog(c *Context, w http.ResponseWriter, r *http.Request) {
forceToDebug := false
if !*c.App.Config().ServiceSettings.EnableDeveloper {
if c.AppContext.Session().UserId == "" {
c.Err = model.NewAppError("postLog", "api.context.permissions.app_error", nil, "", http.StatusForbidden)
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
forceToDebug = true
}
}
m := model.MapFromJSON(r.Body)
lvl := m["level"]
msg := m["message"]
if len(msg) > 400 {
msg = msg[0:399]
}
msg = "Client Logs API Endpoint Message: " + msg
fields := []mlog.Field{
mlog.String("type", "client_message"),
mlog.String("user_agent", c.AppContext.UserAgent()),
}
if !forceToDebug && lvl == "ERROR" {
mlog.Error(msg, fields...)
} else {
mlog.Debug(msg, fields...)
}
m["message"] = msg
w.Write([]byte(model.MapToJSON(m)))
}
func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
teamId := r.URL.Query().Get("team_id")
if name == "" {
name = "standard"
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionGetAnalytics) {
c.SetPermissionError(model.PermissionGetAnalytics)
return
}
rows, err := c.App.GetAnalytics(name, teamId)
if err != nil {
c.Err = err
return
}
if rows == nil {
c.SetInvalidParam("name")
return
}
if err := json.NewEncoder(w).Encode(rows); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err))
}
}
func getLatestVersion(c *Context, w http.ResponseWriter, r *http.Request) {
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("latestVersion", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
resp, err := c.App.GetLatestVersion("https://api.github.com/repos/mattermost/mattermost-server/releases/latest")
if err != nil {
c.Err = err
return
}
b, jsonErr := json.Marshal(resp)
if jsonErr != nil {
c.Logger.Warn("Unable to marshal JSON for latest version.", mlog.Err(jsonErr))
w.WriteHeader(http.StatusInternalServerError)
}
w.Write(b)
}
func getSupportedTimezones(c *Context, w http.ResponseWriter, r *http.Request) {
supportedTimezones := c.App.Timezones().GetSupported()
if supportedTimezones == nil {
supportedTimezones = make([]string, 0)
}
b, err := json.Marshal(supportedTimezones)
if err != nil {
c.Logger.Warn("Unable to marshal JSON in timezones.", mlog.Err(err))
w.WriteHeader(http.StatusInternalServerError)
}
w.Write(b)
}
func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
cfg := model.ConfigFromJSON(r.Body)
if cfg == nil {
cfg = c.App.Config()
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionTestS3) {
c.SetPermissionError(model.PermissionTestS3)
return
}
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
c.Err = model.NewAppError("testS3", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
err := c.App.CheckMandatoryS3Fields(&cfg.FileSettings)
if err != nil {
c.Err = err
return
}
if *cfg.FileSettings.AmazonS3SecretAccessKey == model.FakeSetting {
cfg.FileSettings.AmazonS3SecretAccessKey = c.App.Config().FileSettings.AmazonS3SecretAccessKey
}
appErr := c.App.TestFileStoreConnectionWithConfig(&cfg.FileSettings)
if appErr != nil {
c.Err = appErr
return
}
ReturnStatusOK(w)
}
func getRedirectLocation(c *Context, w http.ResponseWriter, r *http.Request) {
m := make(map[string]string)
m["location"] = ""
if !*c.App.Config().ServiceSettings.EnableLinkPreviews {
w.Write([]byte(model.MapToJSON(m)))
return
}
url := r.URL.Query().Get("url")
if url == "" {
c.SetInvalidParam("url")
return
}
var location string
if err := redirectLocationDataCache.Get(url, &location); err == nil {
m["location"] = location
w.Write([]byte(model.MapToJSON(m)))
return
}
client := c.App.HTTPService().MakeClient(false)
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
res, err := client.Head(url)
if err != nil {
// Cache failures to prevent retries.
redirectLocationDataCache.SetWithExpiry(url, "", 1*time.Hour)
// Always return a success status and a JSON string to limit information returned to client.
w.Write([]byte(model.MapToJSON(m)))
return
}
defer func() {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
}()
location = res.Header.Get("Location")
redirectLocationDataCache.SetWithExpiry(url, location, 1*time.Hour)
m["location"] = location
w.Write([]byte(model.MapToJSON(m)))
}
func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
var ack model.PushNotificationAck
if jsonErr := json.NewDecoder(r.Body).Decode(&ack); jsonErr != nil {
c.Err = model.NewAppError("pushNotificationAck",
"api.push_notifications_ack.message.parse.app_error",
nil,
jsonErr.Error(),
http.StatusBadRequest,
)
return
}
if !*c.App.Config().EmailSettings.SendPushNotifications {
c.Err = model.NewAppError("pushNotificationAck", "api.push_notification.disabled.app_error", nil, "", http.StatusNotImplemented)
return
}
err := c.App.SendAckToPushProxy(&ack)
if ack.IsIdLoaded {
if err != nil {
// Log the error only, then continue to fetch notification message
c.App.NotificationsLog().Error("Notification ack not sent to push proxy",
mlog.String("ackId", ack.Id),
mlog.String("type", ack.NotificationType),
mlog.String("postId", ack.PostId),
mlog.String("status", err.Error()),
)
}
// Return post data only when PostId is passed.
if ack.PostId != "" && ack.NotificationType == model.PushTypeMessage {
if _, appErr := c.App.GetPostIfAuthorized(ack.PostId, c.AppContext.Session()); appErr != nil {
c.Err = appErr
return
}
notificationInterface := c.App.Notification()
if notificationInterface == nil {
c.Err = model.NewAppError("pushNotificationAck", "api.system.id_loaded.not_available.app_error", nil, "", http.StatusFound)
return
}
msg, appError := notificationInterface.GetNotificationMessage(&ack, c.AppContext.Session().UserId)
if appError != nil {
c.Err = model.NewAppError("pushNotificationAck", "api.push_notification.id_loaded.fetch.app_error", nil, appError.Error(), http.StatusInternalServerError)
return
}
if err2 := json.NewEncoder(w).Encode(msg); err2 != nil {
mlog.Warn("Error while writing response", mlog.Err(err2))
}
}
return
} else if err != nil {
c.Err = model.NewAppError("pushNotificationAck", "api.push_notifications_ack.forward.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
ReturnStatusOK(w)
}
func setServerBusy(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
// number of seconds to keep server marked busy
secs := r.URL.Query().Get("seconds")
if secs == "" {
secs = strconv.FormatInt(DefaultServerBusySeconds, 10)
}
i, err := strconv.ParseInt(secs, 10, 64)
if err != nil || i <= 0 || i > MaxServerBusySeconds {
c.SetInvalidURLParam(fmt.Sprintf("seconds must be 1 - %d", MaxServerBusySeconds))
return
}
auditRec := c.MakeAuditRecord("setServerBusy", audit.Fail)
defer c.LogAuditRec(auditRec)
auditRec.AddMeta("seconds", i)
c.App.Srv().Busy.Set(time.Second * time.Duration(i))
mlog.Warn("server busy state activated - non-critical services disabled", mlog.Int64("seconds", i))
auditRec.Success()
ReturnStatusOK(w)
}
func clearServerBusy(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
auditRec := c.MakeAuditRecord("clearServerBusy", audit.Fail)
defer c.LogAuditRec(auditRec)
c.App.Srv().Busy.Clear()
mlog.Info("server busy state cleared - non-critical services enabled")
auditRec.Success()
ReturnStatusOK(w)
}
func getServerBusyExpires(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
// We call to ToJSON because it actually returns a different struct
// along with doing some computations.
sbsJSON, jsonErr := c.App.Srv().Busy.ToJSON()
if jsonErr != nil {
mlog.Warn(jsonErr.Error())
}
if _, err := w.Write(sbsJSON); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err))
}
}
func upgradeToEnterprise(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("upgradeToEnterprise", audit.Fail)
defer c.LogAuditRec(auditRec)
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if model.BuildEnterpriseReady == "true" {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.already-enterprise.app_error", nil, "", http.StatusTooManyRequests)
return
}
percentage, _ := c.App.Srv().UpgradeToE0Status()
if percentage > 0 {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.app_error", nil, "", http.StatusTooManyRequests)
return
}
if percentage == 100 {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.already-done.app_error", nil, "", http.StatusTooManyRequests)
return
}
if err := c.App.Srv().CanIUpgradeToE0(); err != nil {
var ipErr *upgrader.InvalidPermissions
var iaErr *upgrader.InvalidArch
switch {
case errors.As(err, &ipErr):
params := map[string]interface{}{
"MattermostUsername": ipErr.MattermostUsername,
"FileUsername": ipErr.FileUsername,
"Path": ipErr.Path,
}
if ipErr.ErrType == "invalid-user-and-permission" {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-user-and-permission.app_error", params, err.Error(), http.StatusForbidden)
} else if ipErr.ErrType == "invalid-user" {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-user.app_error", params, err.Error(), http.StatusForbidden)
} else if ipErr.ErrType == "invalid-permission" {
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-permission.app_error", params, err.Error(), http.StatusForbidden)
}
case errors.As(err, &iaErr):
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.system_not_supported.app_error", nil, err.Error(), http.StatusForbidden)
default:
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.generic_error.app_error", nil, err.Error(), http.StatusForbidden)
}
return
}
c.App.Srv().Go(func() {
c.App.Srv().UpgradeToE0()
})
auditRec.Success()
w.WriteHeader(http.StatusAccepted)
ReturnStatusOK(w)
}
func upgradeToEnterpriseStatus(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
percentage, err := c.App.Srv().UpgradeToE0Status()
var s map[string]interface{}
if err != nil {
var isErr *upgrader.InvalidSignature
switch {
case errors.As(err, &isErr):
appErr := model.NewAppError("upgradeToEnterpriseStatus", "api.upgrade_to_enterprise_status.app_error", nil, err.Error(), http.StatusBadRequest)
s = map[string]interface{}{"percentage": 0, "error": appErr.Message}
default:
appErr := model.NewAppError("upgradeToEnterpriseStatus", "api.upgrade_to_enterprise_status.signature.app_error", nil, err.Error(), http.StatusBadRequest)
s = map[string]interface{}{"percentage": 0, "error": appErr.Message}
}
} else {
s = map[string]interface{}{"percentage": percentage, "error": nil}
}
w.Write([]byte(model.StringInterfaceToJSON(s)))
}
func restart(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("restartServer", audit.Fail)
defer c.LogAuditRec(auditRec)
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
auditRec.Success()
ReturnStatusOK(w)
time.Sleep(1 * time.Second)
go func() {
c.App.Srv().Restart()
}()
}
func getWarnMetricsStatus(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionToAny(*c.AppContext.Session(), model.SysconsoleReadPermissions) {
c.SetPermissionError(model.SysconsoleReadPermissions...)
return
}
license := c.App.Srv().License()
if license != nil {
mlog.Debug("License is present, skip.")
return
}
status, err := c.App.GetWarnMetricsStatus()
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(status)
if jsonErr != nil {
c.Err = model.NewAppError("getWarnMetricsStatus", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
func sendWarnMetricAckEmail(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("sendWarnMetricAckEmail", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
license := c.App.Srv().License()
if license != nil {
mlog.Debug("License is present, skip.")
return
}
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
if appErr != nil {
c.Err = appErr
return
}
var ack model.SendWarnMetricAck
if jsonErr := json.NewDecoder(r.Body).Decode(&ack); jsonErr != nil {
c.SetInvalidParam("ack")
return
}
appErr = c.App.NotifyAndSetWarnMetricAck(c.Params.WarnMetricId, user, ack.ForceAck, false)
if appErr != nil {
c.Err = appErr
}
auditRec.Success()
ReturnStatusOK(w)
}
func requestTrialLicenseAndAckWarnMetric(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("requestTrialLicenseAndAckWarnMetric", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if model.BuildEnterpriseReady != "true" {
mlog.Debug("Not Enterprise Edition, skip.")
return
}
license := c.App.Srv().License()
if license != nil {
mlog.Debug("License is present, skip.")
return
}
if err := c.App.RequestLicenseAndAckWarnMetric(c.AppContext, c.Params.WarnMetricId, false); err != nil {
c.Err = err
return
}
auditRec.Success()
ReturnStatusOK(w)
}
func getProductNotices(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {
return
}
client, parseError := model.NoticeClientTypeFromString(r.URL.Query().Get("client"))
if parseError != nil {
c.SetInvalidParam("client")
return
}
clientVersion := r.URL.Query().Get("clientVersion")
locale := r.URL.Query().Get("locale")
notices, err := c.App.GetProductNotices(c.AppContext, c.AppContext.Session().UserId, c.Params.TeamId, client, clientVersion, locale)
if err != nil {
c.Err = err
return
}
result, _ := notices.Marshal()
_, _ = w.Write(result)
}
func updateViewedProductNotices(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("updateViewedProductNotices", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
ids := model.ArrayFromJSON(r.Body)
err := c.App.UpdateViewedProductNotices(c.AppContext.Session().UserId, ids)
if err != nil {
c.Err = err
return
}
auditRec.Success()
ReturnStatusOK(w)
}
func getOnboarding(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("getOnboarding", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
firstAdminCompleteSetupObj, err := c.App.GetOnboarding()
if err != nil {
c.Err = model.NewAppError("getOnboarding", "app.system.get_onboarding_request.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
auditRec.Success()
if err := json.NewEncoder(w).Encode(firstAdminCompleteSetupObj); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err))
}
}
func completeOnboarding(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.Err = model.NewAppError("completeOnboarding", "app.system.complete_onboarding_request.no_first_user", nil, "", http.StatusForbidden)
return
}
auditRec := c.MakeAuditRecord("completeOnboarding", audit.Fail)
defer c.LogAuditRec(auditRec)
onboardingRequest, err := model.CompleteOnboardingRequestFromReader(r.Body)
if err != nil {
c.Err = model.NewAppError("completeOnboarding", "app.system.complete_onboarding_request.app_error", nil, err.Error(), http.StatusBadRequest)
return
}
auditRec.AddMeta("install_plugin", onboardingRequest.InstallPlugins)
appErr := c.App.CompleteOnboarding(c.AppContext, onboardingRequest)
if appErr != nil {
c.Err = appErr
return
}
auditRec.Success()
ReturnStatusOK(w)
}