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 }