mirror of
https://github.com/mattermost/mattermost.git
synced 2026-03-06 07:10:52 -05:00
* Moving mlog to corelibs * Regenerating app layers * Fix golangci-lint problem * Fixing golangci-lint errors * Renaming from corelibs to shared * Renaming from corelibs to shared * Fixing import * Fixing merge problems * Fixing build
843 lines
26 KiB
Go
843 lines
26 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/v5/audit"
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"github.com/mattermost/mattermost-server/v5/services/cache"
|
|
"github.com/mattermost/mattermost-server/v5/services/upgrader"
|
|
"github.com/mattermost/mattermost-server/v5/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("/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")
|
|
}
|
|
|
|
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 theoritcally have access to one or more of the system console read permissions
|
|
if !c.App.SessionHasPermissionToAny(*c.App.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, fileBackendErr := c.App.FileBackend()
|
|
if fileBackendErr != nil {
|
|
c.Err = fileBackendErr
|
|
return
|
|
}
|
|
|
|
// 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.STATUS_OK
|
|
s["AndroidLatestVersion"] = reqs.AndroidLatestVersion
|
|
s["AndroidMinVersion"] = reqs.AndroidMinVersion
|
|
s["DesktopLatestVersion"] = reqs.DesktopLatestVersion
|
|
s["DesktopMinVersion"] = reqs.DesktopMinVersion
|
|
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.STATUS_UNHEALTHY
|
|
}
|
|
|
|
// 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.STATUS_OK
|
|
|
|
writeErr := c.App.DBHealthCheckWrite()
|
|
if writeErr != nil {
|
|
mlog.Warn("Unable to write to database.", mlog.Err(writeErr))
|
|
s[dbStatusKey] = model.STATUS_UNHEALTHY
|
|
s[model.STATUS] = model.STATUS_UNHEALTHY
|
|
}
|
|
|
|
writeErr = c.App.DBHealthCheckDelete()
|
|
if writeErr != nil {
|
|
mlog.Warn("Unable to remove ping health check value from database.", mlog.Err(writeErr))
|
|
s[dbStatusKey] = model.STATUS_UNHEALTHY
|
|
s[model.STATUS] = model.STATUS_UNHEALTHY
|
|
}
|
|
|
|
if s[dbStatusKey] == model.STATUS_OK {
|
|
mlog.Debug("Able to write to database.")
|
|
}
|
|
|
|
filestoreStatusKey := "filestore_status"
|
|
s[filestoreStatusKey] = model.STATUS_OK
|
|
appErr := c.App.TestFileStoreConnection()
|
|
if appErr != nil {
|
|
s[filestoreStatusKey] = model.STATUS_UNHEALTHY
|
|
s[model.STATUS] = model.STATUS_UNHEALTHY
|
|
}
|
|
|
|
w.Header().Set(model.STATUS, s[model.STATUS])
|
|
w.Header().Set(dbStatusKey, s[dbStatusKey])
|
|
w.Header().Set(filestoreStatusKey, s[filestoreStatusKey])
|
|
}
|
|
|
|
if s[model.STATUS] != model.STATUS_OK {
|
|
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.App.Session(), model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT)
|
|
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.App.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.App.Session(), model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT)
|
|
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
|
|
}
|
|
|
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT) && siteURL != *c.App.Config().ServiceSettings.SiteURL {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT)
|
|
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.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE)
|
|
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)
|
|
|
|
w.Write([]byte(audits.ToJson()))
|
|
}
|
|
|
|
func databaseRecycle(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT)
|
|
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.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT)
|
|
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.App.Session(), model.PERMISSION_SYSCONSOLE_READ_REPORTING) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_REPORTING)
|
|
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.App.Session().UserId == "" {
|
|
c.Err = model.NewAppError("postLog", "api.context.permissions.app_error", nil, "", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
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.App.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"
|
|
}
|
|
|
|
permissions := []*model.Permission{
|
|
model.PERMISSION_SYSCONSOLE_READ_REPORTING,
|
|
model.PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_USERS,
|
|
}
|
|
if !c.App.SessionHasPermissionToAny(*c.App.Session(), permissions) {
|
|
c.SetPermissionError(permissions...)
|
|
return
|
|
}
|
|
|
|
rows, err := c.App.GetAnalytics(name, teamId)
|
|
if err != nil {
|
|
c.Err = err
|
|
return
|
|
}
|
|
|
|
if rows == nil {
|
|
c.SetInvalidParam("name")
|
|
return
|
|
}
|
|
|
|
w.Write([]byte(rows.ToJson()))
|
|
}
|
|
|
|
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.App.Session(), model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT) {
|
|
c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_ENVIRONMENT)
|
|
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.FAKE_SETTING {
|
|
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) {
|
|
ack, err := model.PushNotificationAckFromJson(r.Body)
|
|
if err != nil {
|
|
c.Err = model.NewAppError("pushNotificationAck",
|
|
"api.push_notifications_ack.message.parse.app_error",
|
|
nil,
|
|
err.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()),
|
|
)
|
|
}
|
|
|
|
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.App.Session().UserId)
|
|
if appError != nil {
|
|
c.Err = model.NewAppError("pushNotificationAck", "api.push_notification.id_loaded.fetch.app_error", nil, appError.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Write([]byte(msg.ToJson()))
|
|
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
return
|
|
}
|
|
w.Write([]byte(c.App.Srv().Busy.ToJson()))
|
|
}
|
|
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.App.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
|
|
}
|
|
|
|
w.Write([]byte(model.MapWarnMetricStatusToJson(status)))
|
|
}
|
|
|
|
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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
return
|
|
}
|
|
|
|
license := c.App.Srv().License()
|
|
if license != nil {
|
|
mlog.Debug("License is present, skip.")
|
|
return
|
|
}
|
|
|
|
user, appErr := c.App.GetUser(c.App.Session().UserId)
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
ack := model.SendWarnMetricAckFromJson(r.Body)
|
|
if ack == 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.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
|
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.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.App.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.App.Session().UserId, ids)
|
|
if err != nil {
|
|
c.Err = err
|
|
return
|
|
}
|
|
|
|
auditRec.Success()
|
|
ReturnStatusOK(w)
|
|
}
|