github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/api4/system.go (about)

     1  // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"runtime"
    11  
    12  	"github.com/xzl8028/xenia-server/mlog"
    13  	"github.com/xzl8028/xenia-server/model"
    14  	"github.com/xzl8028/xenia-server/services/filesstore"
    15  	"github.com/xzl8028/xenia-server/utils"
    16  )
    17  
    18  const REDIRECT_LOCATION_CACHE_SIZE = 10000
    19  
    20  var redirectLocationDataCache = utils.NewLru(REDIRECT_LOCATION_CACHE_SIZE)
    21  
    22  func (api *API) InitSystem() {
    23  	api.BaseRoutes.System.Handle("/ping", api.ApiHandler(getSystemPing)).Methods("GET")
    24  
    25  	api.BaseRoutes.System.Handle("/timezones", api.ApiSessionRequired(getSupportedTimezones)).Methods("GET")
    26  
    27  	api.BaseRoutes.ApiRoot.Handle("/audits", api.ApiSessionRequired(getAudits)).Methods("GET")
    28  	api.BaseRoutes.ApiRoot.Handle("/email/test", api.ApiSessionRequired(testEmail)).Methods("POST")
    29  	api.BaseRoutes.ApiRoot.Handle("/file/s3_test", api.ApiSessionRequired(testS3)).Methods("POST")
    30  	api.BaseRoutes.ApiRoot.Handle("/database/recycle", api.ApiSessionRequired(databaseRecycle)).Methods("POST")
    31  	api.BaseRoutes.ApiRoot.Handle("/caches/invalidate", api.ApiSessionRequired(invalidateCaches)).Methods("POST")
    32  
    33  	api.BaseRoutes.ApiRoot.Handle("/logs", api.ApiSessionRequired(getLogs)).Methods("GET")
    34  	api.BaseRoutes.ApiRoot.Handle("/logs", api.ApiHandler(postLog)).Methods("POST")
    35  
    36  	api.BaseRoutes.ApiRoot.Handle("/analytics/old", api.ApiSessionRequired(getAnalytics)).Methods("GET")
    37  
    38  	api.BaseRoutes.ApiRoot.Handle("/redirect_location", api.ApiSessionRequiredTrustRequester(getRedirectLocation)).Methods("GET")
    39  
    40  	api.BaseRoutes.ApiRoot.Handle("/notifications/ack", api.ApiSessionRequired(pushNotificationAck)).Methods("POST")
    41  }
    42  
    43  func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
    44  	reqs := c.App.Config().ClientRequirements
    45  
    46  	s := make(map[string]string)
    47  	s[model.STATUS] = model.STATUS_OK
    48  	s["AndroidLatestVersion"] = reqs.AndroidLatestVersion
    49  	s["AndroidMinVersion"] = reqs.AndroidMinVersion
    50  	s["DesktopLatestVersion"] = reqs.DesktopLatestVersion
    51  	s["DesktopMinVersion"] = reqs.DesktopMinVersion
    52  	s["IosLatestVersion"] = reqs.IosLatestVersion
    53  	s["IosMinVersion"] = reqs.IosMinVersion
    54  
    55  	actualGoroutines := runtime.NumGoroutine()
    56  	if *c.App.Config().ServiceSettings.GoroutineHealthThreshold > 0 && actualGoroutines >= *c.App.Config().ServiceSettings.GoroutineHealthThreshold {
    57  		mlog.Warn(fmt.Sprintf("The number of running goroutines (%v) is over the health threshold (%v)", actualGoroutines, *c.App.Config().ServiceSettings.GoroutineHealthThreshold))
    58  		s[model.STATUS] = model.STATUS_UNHEALTHY
    59  	}
    60  
    61  	// Enhanced ping health check:
    62  	// If an extra form value is provided then perform extra health checks for
    63  	// database and file storage backends.
    64  	if r.FormValue("get_server_status") != "" {
    65  		dbStatusKey := "database_status"
    66  		s[dbStatusKey] = model.STATUS_OK
    67  		_, appErr := c.App.Srv.Store.System().Get()
    68  		if appErr != nil {
    69  			mlog.Debug(fmt.Sprintf("Unable to get database status: %s", appErr.Error()))
    70  			s[dbStatusKey] = model.STATUS_UNHEALTHY
    71  			s[model.STATUS] = model.STATUS_UNHEALTHY
    72  		}
    73  
    74  		filestoreStatusKey := "filestore_status"
    75  		s[filestoreStatusKey] = model.STATUS_OK
    76  		license := c.App.License()
    77  		backend, appErr := filesstore.NewFileBackend(&c.App.Config().FileSettings, license != nil && *license.Features.Compliance)
    78  		if appErr == nil {
    79  			appErr = backend.TestConnection()
    80  			if appErr != nil {
    81  				s[filestoreStatusKey] = model.STATUS_UNHEALTHY
    82  				s[model.STATUS] = model.STATUS_UNHEALTHY
    83  			}
    84  		} else {
    85  			mlog.Debug(fmt.Sprintf("Unable to get filestore for ping status: %s", appErr.Error()))
    86  			s[filestoreStatusKey] = model.STATUS_UNHEALTHY
    87  			s[model.STATUS] = model.STATUS_UNHEALTHY
    88  		}
    89  
    90  		w.Header().Set(model.STATUS, s[model.STATUS])
    91  		w.Header().Set(dbStatusKey, s[dbStatusKey])
    92  		w.Header().Set(filestoreStatusKey, s[filestoreStatusKey])
    93  	}
    94  
    95  	if s[model.STATUS] != model.STATUS_OK {
    96  		w.WriteHeader(http.StatusInternalServerError)
    97  	}
    98  	w.Write([]byte(model.MapToJson(s)))
    99  }
   100  
   101  func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
   102  	cfg := model.ConfigFromJson(r.Body)
   103  	if cfg == nil {
   104  		cfg = c.App.Config()
   105  	}
   106  
   107  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   108  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   109  		return
   110  	}
   111  
   112  	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
   113  		c.Err = model.NewAppError("testEmail", "api.restricted_system_admin", nil, "", http.StatusForbidden)
   114  		return
   115  	}
   116  
   117  	err := c.App.TestEmail(c.App.Session.UserId, cfg)
   118  	if err != nil {
   119  		c.Err = err
   120  		return
   121  	}
   122  
   123  	ReturnStatusOK(w)
   124  }
   125  
   126  func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
   127  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   128  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   129  		return
   130  	}
   131  
   132  	audits, err := c.App.GetAuditsPage("", c.Params.Page, c.Params.PerPage)
   133  
   134  	if err != nil {
   135  		c.Err = err
   136  		return
   137  	}
   138  
   139  	w.Write([]byte(audits.ToJson()))
   140  }
   141  
   142  func databaseRecycle(c *Context, w http.ResponseWriter, r *http.Request) {
   143  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   144  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   145  		return
   146  	}
   147  
   148  	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
   149  		c.Err = model.NewAppError("databaseRecycle", "api.restricted_system_admin", nil, "", http.StatusForbidden)
   150  		return
   151  	}
   152  
   153  	c.App.RecycleDatabaseConnection()
   154  
   155  	ReturnStatusOK(w)
   156  }
   157  
   158  func invalidateCaches(c *Context, w http.ResponseWriter, r *http.Request) {
   159  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   160  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   161  		return
   162  	}
   163  
   164  	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
   165  		c.Err = model.NewAppError("invalidateCaches", "api.restricted_system_admin", nil, "", http.StatusForbidden)
   166  		return
   167  	}
   168  
   169  	err := c.App.InvalidateAllCaches()
   170  	if err != nil {
   171  		c.Err = err
   172  		return
   173  	}
   174  
   175  	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
   176  	ReturnStatusOK(w)
   177  }
   178  
   179  func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
   180  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   181  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   182  		return
   183  	}
   184  
   185  	lines, err := c.App.GetLogs(c.Params.Page, c.Params.LogsPerPage)
   186  	if err != nil {
   187  		c.Err = err
   188  		return
   189  	}
   190  
   191  	w.Write([]byte(model.ArrayToJson(lines)))
   192  }
   193  
   194  func postLog(c *Context, w http.ResponseWriter, r *http.Request) {
   195  	forceToDebug := false
   196  
   197  	if !*c.App.Config().ServiceSettings.EnableDeveloper {
   198  		if c.App.Session.UserId == "" {
   199  			c.Err = model.NewAppError("postLog", "api.context.permissions.app_error", nil, "", http.StatusForbidden)
   200  			return
   201  		}
   202  
   203  		if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   204  			forceToDebug = true
   205  		}
   206  	}
   207  
   208  	m := model.MapFromJson(r.Body)
   209  	lvl := m["level"]
   210  	msg := m["message"]
   211  
   212  	if len(msg) > 400 {
   213  		msg = msg[0:399]
   214  	}
   215  
   216  	if !forceToDebug && lvl == "ERROR" {
   217  		err := &model.AppError{}
   218  		err.Message = msg
   219  		err.Id = msg
   220  		err.Where = "client"
   221  		c.LogError(err)
   222  	} else {
   223  		mlog.Debug(fmt.Sprint(msg))
   224  	}
   225  
   226  	m["message"] = msg
   227  	w.Write([]byte(model.MapToJson(m)))
   228  }
   229  
   230  func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
   231  	name := r.URL.Query().Get("name")
   232  	teamId := r.URL.Query().Get("team_id")
   233  
   234  	if name == "" {
   235  		name = "standard"
   236  	}
   237  
   238  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   239  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   240  		return
   241  	}
   242  
   243  	rows, err := c.App.GetAnalytics(name, teamId)
   244  	if err != nil {
   245  		c.Err = err
   246  		return
   247  	}
   248  
   249  	if rows == nil {
   250  		c.SetInvalidParam("name")
   251  		return
   252  	}
   253  
   254  	w.Write([]byte(rows.ToJson()))
   255  }
   256  
   257  func getSupportedTimezones(c *Context, w http.ResponseWriter, r *http.Request) {
   258  	supportedTimezones := c.App.Timezones.GetSupported()
   259  	if supportedTimezones == nil {
   260  		supportedTimezones = make([]string, 0)
   261  	}
   262  
   263  	b, err := json.Marshal(supportedTimezones)
   264  	if err != nil {
   265  		c.Log.Warn("Unable to marshal JSON in timezones.", mlog.Err(err))
   266  		w.WriteHeader(http.StatusInternalServerError)
   267  	}
   268  
   269  	w.Write(b)
   270  }
   271  
   272  func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
   273  	cfg := model.ConfigFromJson(r.Body)
   274  	if cfg == nil {
   275  		cfg = c.App.Config()
   276  	}
   277  
   278  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
   279  		c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   280  		return
   281  	}
   282  
   283  	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
   284  		c.Err = model.NewAppError("testS3", "api.restricted_system_admin", nil, "", http.StatusForbidden)
   285  		return
   286  	}
   287  
   288  	err := filesstore.CheckMandatoryS3Fields(&cfg.FileSettings)
   289  	if err != nil {
   290  		c.Err = err
   291  		return
   292  	}
   293  
   294  	if *cfg.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING {
   295  		cfg.FileSettings.AmazonS3SecretAccessKey = c.App.Config().FileSettings.AmazonS3SecretAccessKey
   296  	}
   297  
   298  	license := c.App.License()
   299  	backend, appErr := filesstore.NewFileBackend(&cfg.FileSettings, license != nil && *license.Features.Compliance)
   300  	if appErr == nil {
   301  		appErr = backend.TestConnection()
   302  	}
   303  	if appErr != nil {
   304  		c.Err = appErr
   305  		return
   306  	}
   307  
   308  	ReturnStatusOK(w)
   309  }
   310  
   311  func getRedirectLocation(c *Context, w http.ResponseWriter, r *http.Request) {
   312  	m := make(map[string]string)
   313  	m["location"] = ""
   314  
   315  	if !*c.App.Config().ServiceSettings.EnableLinkPreviews {
   316  		w.Write([]byte(model.MapToJson(m)))
   317  		return
   318  	}
   319  
   320  	url := r.URL.Query().Get("url")
   321  	if len(url) == 0 {
   322  		c.SetInvalidParam("url")
   323  		return
   324  	}
   325  
   326  	if location, ok := redirectLocationDataCache.Get(url); ok {
   327  		m["location"] = location.(string)
   328  		w.Write([]byte(model.MapToJson(m)))
   329  		return
   330  	}
   331  
   332  	client := c.App.HTTPService.MakeClient(false)
   333  	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
   334  		return http.ErrUseLastResponse
   335  	}
   336  
   337  	res, err := client.Head(url)
   338  	if err != nil {
   339  		// Cache failures to prevent retries.
   340  		redirectLocationDataCache.AddWithExpiresInSecs(url, "", 3600) // Expires after 1 hour
   341  		// Always return a success status and a JSON string to limit information returned to client.
   342  		w.Write([]byte(model.MapToJson(m)))
   343  		return
   344  	}
   345  
   346  	location := res.Header.Get("Location")
   347  	redirectLocationDataCache.AddWithExpiresInSecs(url, location, 3600) // Expires after 1 hour
   348  	m["location"] = location
   349  
   350  	w.Write([]byte(model.MapToJson(m)))
   351  	return
   352  }
   353  
   354  func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
   355  	ack := model.PushNotificationAckFromJson(r.Body)
   356  
   357  	if !*c.App.Config().EmailSettings.SendPushNotifications {
   358  		c.Err = model.NewAppError("pushNotificationAck", "api.push_notification.disabled.app_error", nil, "", http.StatusNotImplemented)
   359  		return
   360  	}
   361  
   362  	err := c.App.SendAckToPushProxy(ack)
   363  	if err != nil {
   364  		c.Err = model.NewAppError("pushNotificationAck", "api.push_notifications_ack.forward.app_error", nil, err.Error(), http.StatusInternalServerError)
   365  		return
   366  	}
   367  
   368  	ReturnStatusOK(w)
   369  	return
   370  }