2017-04-12 08:27:57 -04:00
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2017-02-17 11:57:19 +01:00
// See License.txt for license information.
package api4
import (
2018-12-17 08:51:46 -08:00
"encoding/json"
2018-04-27 12:49:45 -07:00
"fmt"
2017-02-17 11:57:19 +01:00
"net/http"
2017-05-31 06:47:27 +02:00
"runtime"
2019-08-23 00:25:50 +02:00
"time"
2017-02-17 11:57:19 +01:00
2018-04-27 12:49:45 -07:00
"github.com/mattermost/mattermost-server/mlog"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/model"
2018-09-20 19:07:03 +02:00
"github.com/mattermost/mattermost-server/services/filesstore"
2019-01-27 18:24:46 -08:00
"github.com/mattermost/mattermost-server/utils"
2017-02-17 11:57:19 +01:00
)
2019-01-27 18:24:46 -08:00
const REDIRECT_LOCATION_CACHE_SIZE = 10000
var redirectLocationDataCache = utils . NewLru ( REDIRECT_LOCATION_CACHE_SIZE )
2017-09-22 12:54:27 -05:00
func ( api * API ) InitSystem ( ) {
api . BaseRoutes . System . Handle ( "/ping" , api . ApiHandler ( getSystemPing ) ) . Methods ( "GET" )
2017-05-31 06:47:27 +02:00
2018-03-22 06:53:43 -07:00
api . BaseRoutes . System . Handle ( "/timezones" , api . ApiSessionRequired ( getSupportedTimezones ) ) . Methods ( "GET" )
2017-09-22 12:54:27 -05:00
api . BaseRoutes . ApiRoot . Handle ( "/audits" , api . ApiSessionRequired ( getAudits ) ) . Methods ( "GET" )
api . BaseRoutes . ApiRoot . Handle ( "/email/test" , api . ApiSessionRequired ( testEmail ) ) . Methods ( "POST" )
2018-03-01 00:12:11 +01:00
api . BaseRoutes . ApiRoot . Handle ( "/file/s3_test" , api . ApiSessionRequired ( testS3 ) ) . Methods ( "POST" )
2017-09-22 12:54:27 -05:00
api . BaseRoutes . ApiRoot . Handle ( "/database/recycle" , api . ApiSessionRequired ( databaseRecycle ) ) . Methods ( "POST" )
api . BaseRoutes . ApiRoot . Handle ( "/caches/invalidate" , api . ApiSessionRequired ( invalidateCaches ) ) . Methods ( "POST" )
2017-03-16 14:59:44 -04:00
2017-09-22 12:54:27 -05:00
api . BaseRoutes . ApiRoot . Handle ( "/logs" , api . ApiSessionRequired ( getLogs ) ) . Methods ( "GET" )
2017-10-20 20:26:12 -04:00
api . BaseRoutes . ApiRoot . Handle ( "/logs" , api . ApiHandler ( postLog ) ) . Methods ( "POST" )
2017-06-19 16:35:53 -04:00
2017-09-22 12:54:27 -05:00
api . BaseRoutes . ApiRoot . Handle ( "/analytics/old" , api . ApiSessionRequired ( getAnalytics ) ) . Methods ( "GET" )
2018-08-24 08:49:31 -04:00
api . BaseRoutes . ApiRoot . Handle ( "/redirect_location" , api . ApiSessionRequiredTrustRequester ( getRedirectLocation ) ) . Methods ( "GET" )
2019-04-30 18:15:29 -04:00
api . BaseRoutes . ApiRoot . Handle ( "/notifications/ack" , api . ApiSessionRequired ( pushNotificationAck ) ) . Methods ( "POST" )
2017-02-17 11:57:19 +01:00
}
func getSystemPing ( c * Context , w http . ResponseWriter , r * http . Request ) {
2019-06-20 16:06:04 -04:00
reqs := c . App . Config ( ) . ClientRequirements
2017-05-31 06:47:27 +02:00
2019-06-20 16:06:04 -04:00
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
2017-08-28 09:22:54 -07:00
2019-06-20 16:06:04 -04:00
actualGoroutines := runtime . NumGoroutine ( )
if * c . App . Config ( ) . ServiceSettings . GoroutineHealthThreshold > 0 && actualGoroutines >= * c . App . Config ( ) . ServiceSettings . GoroutineHealthThreshold {
mlog . Warn ( fmt . Sprintf ( "The number of running goroutines (%v) is over the health threshold (%v)" , actualGoroutines , * c . App . Config ( ) . ServiceSettings . GoroutineHealthThreshold ) )
s [ model . STATUS ] = model . STATUS_UNHEALTHY
}
2017-05-31 06:47:27 +02:00
2019-06-20 16:06:04 -04:00
// 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
2019-08-23 00:25:50 +02:00
// Database Write/Read Check
currentTime := fmt . Sprintf ( "%d" , time . Now ( ) . Unix ( ) )
healthCheckKey := "health_check"
writeErr := c . App . Srv . Store . System ( ) . SaveOrUpdate ( & model . System {
Name : healthCheckKey ,
Value : currentTime ,
} )
if writeErr != nil {
mlog . Debug ( fmt . Sprintf ( "Unable to write to database: %s" , writeErr . Error ( ) ) )
2019-06-20 16:06:04 -04:00
s [ dbStatusKey ] = model . STATUS_UNHEALTHY
s [ model . STATUS ] = model . STATUS_UNHEALTHY
2019-08-23 00:25:50 +02:00
} else {
healthCheck , readErr := c . App . Srv . Store . System ( ) . GetByName ( healthCheckKey )
if readErr != nil {
mlog . Debug ( fmt . Sprintf ( "Unable to read from database: %s" , readErr . Error ( ) ) )
s [ dbStatusKey ] = model . STATUS_UNHEALTHY
s [ model . STATUS ] = model . STATUS_UNHEALTHY
} else if healthCheck . Value != currentTime {
mlog . Debug ( fmt . Sprintf ( "Incorrect healthcheck value, expected %s, got %s" , currentTime , healthCheck . Value ) )
s [ dbStatusKey ] = model . STATUS_UNHEALTHY
s [ model . STATUS ] = model . STATUS_UNHEALTHY
} else {
mlog . Debug ( "Able to write/read files to database" )
}
2019-06-20 16:06:04 -04:00
}
filestoreStatusKey := "filestore_status"
s [ filestoreStatusKey ] = model . STATUS_OK
license := c . App . License ( )
backend , appErr := filesstore . NewFileBackend ( & c . App . Config ( ) . FileSettings , license != nil && * license . Features . Compliance )
if appErr == nil {
appErr = backend . TestConnection ( )
if appErr != nil {
s [ filestoreStatusKey ] = model . STATUS_UNHEALTHY
s [ model . STATUS ] = model . STATUS_UNHEALTHY
}
} else {
mlog . Debug ( fmt . Sprintf ( "Unable to get filestore for ping status: %s" , appErr . Error ( ) ) )
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 ] )
}
2017-05-31 06:47:27 +02:00
2019-06-20 16:06:04 -04:00
if s [ model . STATUS ] != model . STATUS_OK {
2017-05-31 06:47:27 +02:00
w . WriteHeader ( http . StatusInternalServerError )
}
2019-06-20 16:06:04 -04:00
w . Write ( [ ] byte ( model . MapToJson ( s ) ) )
2017-02-17 11:57:19 +01:00
}
2017-03-13 13:27:27 +01:00
2017-03-13 16:09:00 +01:00
func testEmail ( c * Context , w http . ResponseWriter , r * http . Request ) {
2017-06-14 08:56:56 -04:00
cfg := model . ConfigFromJson ( r . Body )
if cfg == nil {
2017-10-18 15:36:43 -07:00
cfg = c . App . Config ( )
2017-06-14 08:56:56 -04:00
}
2017-03-13 16:09:00 +01:00
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-03-13 16:09:00 +01:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2019-03-08 13:15:28 -05:00
if * c . App . Config ( ) . ExperimentalSettings . RestrictSystemAdmin {
c . Err = model . NewAppError ( "testEmail" , "api.restricted_system_admin" , nil , "" , http . StatusForbidden )
2017-03-20 13:37:34 +01:00
return
}
2019-03-08 13:15:28 -05:00
err := c . App . TestEmail ( c . App . Session . UserId , cfg )
2017-03-20 13:37:34 +01:00
if err != nil {
c . Err = err
return
}
2019-03-08 13:15:28 -05:00
ReturnStatusOK ( w )
2017-03-20 13:37:34 +01:00
}
2017-03-21 09:06:08 -04:00
func getAudits ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-03-21 09:06:08 -04:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2017-09-06 17:12:54 -05:00
audits , err := c . App . GetAuditsPage ( "" , c . Params . Page , c . Params . PerPage )
2017-03-21 09:06:08 -04:00
if err != nil {
c . Err = err
return
}
w . Write ( [ ] byte ( audits . ToJson ( ) ) )
}
2017-03-13 16:47:33 +01:00
func databaseRecycle ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-03-13 16:47:33 +01:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2019-03-08 13:15:28 -05:00
if * c . App . Config ( ) . ExperimentalSettings . RestrictSystemAdmin {
c . Err = model . NewAppError ( "databaseRecycle" , "api.restricted_system_admin" , nil , "" , http . StatusForbidden )
return
}
2017-09-06 17:12:54 -05:00
c . App . RecycleDatabaseConnection ( )
2017-03-13 16:47:33 +01:00
ReturnStatusOK ( w )
}
2017-03-14 17:06:07 +01:00
func invalidateCaches ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-03-14 17:06:07 +01:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2019-03-08 13:15:28 -05:00
if * c . App . Config ( ) . ExperimentalSettings . RestrictSystemAdmin {
c . Err = model . NewAppError ( "invalidateCaches" , "api.restricted_system_admin" , nil , "" , http . StatusForbidden )
return
}
2017-09-06 17:12:54 -05:00
err := c . App . InvalidateAllCaches ( )
2017-03-14 17:06:07 +01:00
if err != nil {
c . Err = err
return
}
w . Header ( ) . Set ( "Cache-Control" , "no-cache, no-store, must-revalidate" )
ReturnStatusOK ( w )
}
2017-03-16 14:59:44 -04:00
func getLogs ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-03-16 14:59:44 -04:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2017-12-14 04:04:55 +09:00
lines , err := c . App . GetLogs ( c . Params . Page , c . Params . LogsPerPage )
2017-03-16 14:59:44 -04:00
if err != nil {
c . Err = err
return
}
w . Write ( [ ] byte ( model . ArrayToJson ( lines ) ) )
}
2017-03-27 09:19:53 -04:00
2017-04-21 11:16:35 +02:00
func postLog ( c * Context , w http . ResponseWriter , r * http . Request ) {
2017-10-20 20:26:12 -04:00
forceToDebug := false
if ! * c . App . Config ( ) . ServiceSettings . EnableDeveloper {
2018-11-28 10:56:21 -08:00
if c . App . Session . UserId == "" {
2017-10-20 20:26:12 -04:00
c . Err = model . NewAppError ( "postLog" , "api.context.permissions.app_error" , nil , "" , http . StatusForbidden )
return
}
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-10-20 20:26:12 -04:00
forceToDebug = true
}
2017-04-21 11:16:35 +02:00
}
m := model . MapFromJson ( r . Body )
lvl := m [ "level" ]
msg := m [ "message" ]
if len ( msg ) > 400 {
msg = msg [ 0 : 399 ]
}
2017-10-20 20:26:12 -04:00
if ! forceToDebug && lvl == "ERROR" {
2017-04-21 11:16:35 +02:00
err := & model . AppError { }
err . Message = msg
err . Id = msg
err . Where = "client"
c . LogError ( err )
} else {
2018-04-27 12:49:45 -07:00
mlog . Debug ( fmt . Sprint ( msg ) )
2017-04-21 11:16:35 +02:00
}
m [ "message" ] = msg
w . Write ( [ ] byte ( model . MapToJson ( m ) ) )
}
2017-06-19 16:35:53 -04:00
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"
}
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2017-06-19 16:35:53 -04:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2017-09-06 17:12:54 -05:00
rows , err := c . App . GetAnalytics ( name , teamId )
2017-06-19 16:35:53 -04:00
if err != nil {
c . Err = err
return
}
if rows == nil {
c . SetInvalidParam ( "name" )
return
}
w . Write ( [ ] byte ( rows . ToJson ( ) ) )
}
2018-03-01 00:12:11 +01:00
2018-03-22 06:53:43 -07:00
func getSupportedTimezones ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-12-17 08:51:46 -08:00
supportedTimezones := c . App . Timezones . GetSupported ( )
if supportedTimezones == nil {
supportedTimezones = make ( [ ] string , 0 )
}
2018-03-22 06:53:43 -07:00
2018-12-17 08:51:46 -08:00
b , err := json . Marshal ( supportedTimezones )
if err != nil {
c . Log . Warn ( "Unable to marshal JSON in timezones." , mlog . Err ( err ) )
w . WriteHeader ( http . StatusInternalServerError )
2018-03-22 06:53:43 -07:00
}
2018-12-17 08:51:46 -08:00
w . Write ( b )
2018-03-22 06:53:43 -07:00
}
2018-03-01 00:12:11 +01:00
func testS3 ( c * Context , w http . ResponseWriter , r * http . Request ) {
cfg := model . ConfigFromJson ( r . Body )
if cfg == nil {
cfg = c . App . Config ( )
}
2018-11-28 10:56:21 -08:00
if ! c . App . SessionHasPermissionTo ( c . App . Session , model . PERMISSION_MANAGE_SYSTEM ) {
2018-03-01 00:12:11 +01:00
c . SetPermissionError ( model . PERMISSION_MANAGE_SYSTEM )
return
}
2019-03-08 13:15:28 -05:00
if * c . App . Config ( ) . ExperimentalSettings . RestrictSystemAdmin {
c . Err = model . NewAppError ( "testS3" , "api.restricted_system_admin" , nil , "" , http . StatusForbidden )
return
}
2018-09-20 19:07:03 +02:00
err := filesstore . CheckMandatoryS3Fields ( & cfg . FileSettings )
2018-03-01 00:12:11 +01:00
if err != nil {
c . Err = err
return
}
2019-01-31 08:12:01 -05:00
if * cfg . FileSettings . AmazonS3SecretAccessKey == model . FAKE_SETTING {
2018-03-13 17:26:56 +01:00
cfg . FileSettings . AmazonS3SecretAccessKey = c . App . Config ( ) . FileSettings . AmazonS3SecretAccessKey
}
2018-03-01 00:12:11 +01:00
license := c . App . License ( )
2018-09-20 19:07:03 +02:00
backend , appErr := filesstore . NewFileBackend ( & cfg . FileSettings , license != nil && * license . Features . Compliance )
2018-03-01 00:12:11 +01:00
if appErr == nil {
appErr = backend . TestConnection ( )
}
if appErr != nil {
c . Err = appErr
return
}
ReturnStatusOK ( w )
}
2018-08-24 08:49:31 -04:00
func getRedirectLocation ( c * Context , w http . ResponseWriter , r * http . Request ) {
2018-10-02 09:00:50 +03:00
m := make ( map [ string ] string )
m [ "location" ] = ""
2019-01-27 18:24:46 -08:00
2019-02-12 08:37:54 -05:00
if ! * c . App . Config ( ) . ServiceSettings . EnableLinkPreviews {
2018-10-02 09:00:50 +03:00
w . Write ( [ ] byte ( model . MapToJson ( m ) ) )
return
}
2019-01-27 18:24:46 -08:00
2018-08-24 08:49:31 -04:00
url := r . URL . Query ( ) . Get ( "url" )
if len ( url ) == 0 {
c . SetInvalidParam ( "url" )
return
}
2019-02-01 08:13:51 -08:00
if location , ok := redirectLocationDataCache . Get ( url ) ; ok {
2019-01-27 18:24:46 -08:00
m [ "location" ] = location . ( string )
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
2018-08-24 08:49:31 -04:00
}
res , err := client . Head ( url )
if err != nil {
2019-01-27 18:24:46 -08:00
// Cache failures to prevent retries.
redirectLocationDataCache . AddWithExpiresInSecs ( url , "" , 3600 ) // Expires after 1 hour
// Always return a success status and a JSON string to limit information returned to client.
2018-08-24 08:49:31 -04:00
w . Write ( [ ] byte ( model . MapToJson ( m ) ) )
return
}
2019-01-27 18:24:46 -08:00
location := res . Header . Get ( "Location" )
redirectLocationDataCache . AddWithExpiresInSecs ( url , location , 3600 ) // Expires after 1 hour
m [ "location" ] = location
2018-08-24 08:49:31 -04:00
w . Write ( [ ] byte ( model . MapToJson ( m ) ) )
return
}
2019-04-30 18:15:29 -04:00
func pushNotificationAck ( c * Context , w http . ResponseWriter , r * http . Request ) {
ack := model . PushNotificationAckFromJson ( r . Body )
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 err != nil {
c . Err = model . NewAppError ( "pushNotificationAck" , "api.push_notifications_ack.forward.app_error" , nil , err . Error ( ) , http . StatusInternalServerError )
return
}
ReturnStatusOK ( w )
return
}