* Migrate admin functions into app package

* More user function refactoring

* Move post functions into app package
Этот коммит содержится в:
Joram Wilander
2017-01-25 09:32:42 -05:00
коммит произвёл Harrison Healey
родитель 8ed665cb76
Коммит d245b29f82
24 изменённых файлов: 1466 добавлений и 1081 удалений

Просмотреть файл

@@ -4,32 +4,17 @@
package api package api
import ( import (
"bufio"
"io"
"io/ioutil"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings"
"time"
"runtime/debug"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/platform/app" "github.com/mattermost/platform/app"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
"github.com/mssola/user_agent" "github.com/mssola/user_agent"
) )
const (
DAY_MILLISECONDS = 24 * 60 * 60 * 1000
MONTH_MILLISECONDS = 31 * DAY_MILLISECONDS
)
func InitAdmin() { func InitAdmin() {
l4g.Debug(utils.T("api.admin.init.debug")) l4g.Debug(utils.T("api.admin.init.debug"))
@@ -61,68 +46,28 @@ func InitAdmin() {
} }
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) { func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
lines, err := GetLogs() lines, err := app.GetLogs()
if err != nil { if err != nil {
c.Err = err c.Err = err
return return
} }
if einterfaces.GetClusterInterface() != nil {
clines, err := einterfaces.GetClusterInterface().GetLogs()
if err != nil {
c.Err = err
return
}
lines = append(lines, clines...)
}
w.Write([]byte(model.ArrayToJson(lines))) w.Write([]byte(model.ArrayToJson(lines)))
} }
func GetLogs() ([]string, *model.AppError) {
var lines []string
if utils.Cfg.LogSettings.EnableFile {
file, err := os.Open(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation))
if err != nil {
return nil, model.NewLocAppError("getLogs", "api.admin.file_read_error", nil, err.Error())
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
} else {
lines = append(lines, "")
}
return lines, nil
}
func getClusterStatus(c *Context, w http.ResponseWriter, r *http.Request) { func getClusterStatus(c *Context, w http.ResponseWriter, r *http.Request) {
infos := make([]*model.ClusterInfo, 0) infos := app.GetClusterStatus()
if einterfaces.GetClusterInterface() != nil {
infos = einterfaces.GetClusterInterface().GetClusterInfos()
}
w.Write([]byte(model.ClusterInfosToJson(infos))) w.Write([]byte(model.ClusterInfosToJson(infos)))
} }
func getAllAudits(c *Context, w http.ResponseWriter, r *http.Request) { func getAllAudits(c *Context, w http.ResponseWriter, r *http.Request) {
if result := <-app.Srv.Store.Audit().Get("", 200); result.Err != nil { if audits, err := app.GetAudits("", 200); err != nil {
c.Err = result.Err c.Err = err
return
} else if HandleEtag(audits.Etag(), "Get All Audits", w, r) {
return return
} else { } else {
audits := result.Data.(model.Audits)
etag := audits.Etag() etag := audits.Etag()
if HandleEtag(etag, "Get All Audits", w, r) {
return
}
if len(etag) > 0 { if len(etag) > 0 {
w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Header().Set(model.HEADER_ETAG_SERVER, etag)
} }
@@ -133,38 +78,22 @@ func getAllAudits(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
json := utils.Cfg.ToJson() cfg := app.GetConfig()
cfg := model.ConfigFromJson(strings.NewReader(json))
cfg.Sanitize()
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write([]byte(cfg.ToJson())) w.Write([]byte(cfg.ToJson()))
} }
func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) { func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) {
debug.FreeOSMemory() app.ReloadConfig()
utils.LoadConfig(utils.CfgFileName)
// start/restart email batching job if necessary
app.InitEmailBatching()
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
ReturnStatusOK(w) ReturnStatusOK(w)
} }
func invalidateAllCaches(c *Context, w http.ResponseWriter, r *http.Request) { func invalidateAllCaches(c *Context, w http.ResponseWriter, r *http.Request) {
debug.FreeOSMemory() err := app.InvalidateAllCaches()
if err != nil {
app.InvalidateAllCaches() c.Err = err
return
if einterfaces.GetClusterInterface() != nil {
err := einterfaces.GetClusterInterface().InvalidateAllCaches()
if err != nil {
c.Err = err
return
}
} }
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
@@ -178,66 +107,18 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
cfg.SetDefaults() err := app.SaveConfig(cfg)
utils.Desanitize(cfg) if err != nil {
if err := cfg.IsValid(); err != nil {
c.Err = err c.Err = err
return return
} }
if err := utils.ValidateLdapFilter(cfg); err != nil {
c.Err = err
return
}
if *utils.Cfg.ClusterSettings.Enable {
c.Err = model.NewLocAppError("saveConfig", "ent.cluster.save_config.error", nil, "")
return
}
c.LogAudit("") c.LogAudit("")
ReturnStatusOK(w)
//oldCfg := utils.Cfg
utils.SaveConfig(utils.CfgFileName, cfg)
utils.LoadConfig(utils.CfgFileName)
if einterfaces.GetMetricsInterface() != nil {
if *utils.Cfg.MetricsSettings.Enable {
einterfaces.GetMetricsInterface().StartServer()
} else {
einterfaces.GetMetricsInterface().StopServer()
}
}
// Future feature is to sync the configuration files
// if einterfaces.GetClusterInterface() != nil {
// err := einterfaces.GetClusterInterface().ConfigChanged(cfg, oldCfg, true)
// if err != nil {
// c.Err = err
// return
// }
// }
// start/restart email batching job if necessary
app.InitEmailBatching()
rdata := map[string]string{}
rdata["status"] = "OK"
w.Write([]byte(model.MapToJson(rdata)))
} }
func recycleDatabaseConnection(c *Context, w http.ResponseWriter, r *http.Request) { func recycleDatabaseConnection(c *Context, w http.ResponseWriter, r *http.Request) {
oldStore := app.Srv.Store app.RecycleDatabaseConnection()
l4g.Warn(utils.T("api.admin.recycle_db_start.warn"))
app.Srv.Store = store.NewSqlStore()
time.Sleep(20 * time.Second)
oldStore.Close()
l4g.Warn(utils.T("api.admin.recycle_db_end.warn"))
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
ReturnStatusOK(w) ReturnStatusOK(w)
} }
@@ -249,60 +130,27 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if len(cfg.EmailSettings.SMTPServer) == 0 { err := app.TestEmail(c.Session.UserId, cfg)
c.Err = model.NewLocAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"})) if err != nil {
c.Err = err
return return
} }
// if the user hasn't changed their email settings, fill in the actual SMTP password so that
// the user can verify an existing SMTP connection
if cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
if cfg.EmailSettings.SMTPServer == utils.Cfg.EmailSettings.SMTPServer &&
cfg.EmailSettings.SMTPPort == utils.Cfg.EmailSettings.SMTPPort &&
cfg.EmailSettings.SMTPUsername == utils.Cfg.EmailSettings.SMTPUsername {
cfg.EmailSettings.SMTPPassword = utils.Cfg.EmailSettings.SMTPPassword
} else {
c.Err = model.NewLocAppError("testEmail", "api.admin.test_email.reenter_password", nil, "")
return
}
}
if result := <-app.Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
c.Err = result.Err
return
} else {
if err := utils.SendMailUsingConfig(result.Data.(*model.User).Email, c.T("api.admin.test_email.subject"), c.T("api.admin.test_email.body"), cfg); err != nil {
c.Err = err
return
}
}
m := make(map[string]string) m := make(map[string]string)
m["SUCCESS"] = "true" m["SUCCESS"] = "true"
w.Write([]byte(model.MapToJson(m))) w.Write([]byte(model.MapToJson(m)))
} }
func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) { func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance { crs, err := app.GetComplianceReports()
c.Err = model.NewLocAppError("getComplianceReports", "ent.compliance.licence_disable.app_error", nil, "") if err != nil {
c.Err = err
return return
} }
w.Write([]byte(crs.ToJson()))
if result := <-app.Srv.Store.Compliance().GetAll(); result.Err != nil {
c.Err = result.Err
return
} else {
crs := result.Data.(model.Compliances)
w.Write([]byte(crs.ToJson()))
}
} }
func saveComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { func saveComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance || einterfaces.GetComplianceInterface() == nil {
c.Err = model.NewLocAppError("saveComplianceReport", "ent.compliance.licence_disable.app_error", nil, "")
return
}
job := model.ComplianceFromJson(r.Body) job := model.ComplianceFromJson(r.Body)
if job == nil { if job == nil {
c.SetInvalidParam("saveComplianceReport", "compliance") c.SetInvalidParam("saveComplianceReport", "compliance")
@@ -310,25 +158,18 @@ func saveComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
} }
job.UserId = c.Session.UserId job.UserId = c.Session.UserId
job.Type = model.COMPLIANCE_TYPE_ADHOC
if result := <-app.Srv.Store.Compliance().Save(job); result.Err != nil { rjob, err := app.SaveComplianceReport(job)
c.Err = result.Err if err != nil {
c.Err = err
return return
} else {
job = result.Data.(*model.Compliance)
go einterfaces.GetComplianceInterface().RunComplianceJob(job)
} }
w.Write([]byte(job.ToJson())) c.LogAudit("")
w.Write([]byte(rjob.ToJson()))
} }
func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance || einterfaces.GetComplianceInterface() == nil {
c.Err = model.NewLocAppError("downloadComplianceReport", "ent.compliance.licence_disable.app_error", nil, "")
return
}
params := mux.Vars(r) params := mux.Vars(r)
id := params["id"] id := params["id"]
@@ -337,35 +178,36 @@ func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request
return return
} }
if result := <-app.Srv.Store.Compliance().Get(id); result.Err != nil { job, err := app.GetComplianceReport(id)
c.Err = result.Err if err != nil {
c.Err = err
return return
} else {
job := result.Data.(*model.Compliance)
c.LogAudit("downloaded " + job.Desc)
if f, err := ioutil.ReadFile(*utils.Cfg.ComplianceSettings.Directory + "compliance/" + job.JobName() + ".zip"); err != nil {
c.Err = model.NewLocAppError("readFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
return
} else {
w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set("Content-Length", strconv.Itoa(len(f)))
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
// attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent())
bname, _ := ua.Browser()
w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"")
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" {
// trim off anything before the final / so we just get the file's name
w.Header().Set("Content-Type", "application/octet-stream")
}
w.Write(f)
}
} }
reportBytes, err := app.GetComplianceFile(job)
if err != nil {
c.Err = err
return
}
c.LogAudit("downloaded " + job.Desc)
w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set("Content-Length", strconv.Itoa(len(reportBytes)))
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
// attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent())
bname, _ := ua.Browser()
w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"")
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" {
// trim off anything before the final / so we just get the file's name
w.Header().Set("Content-Type", "application/octet-stream")
}
w.Write(reportBytes)
} }
func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -373,237 +215,18 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
teamId := params["id"] teamId := params["id"]
name := params["name"] name := params["name"]
skipIntensiveQueries := false rows, err := app.GetAnalytics(name, teamId)
var systemUserCount int64 if err != nil {
if r := <-app.Srv.Store.User().AnalyticsUniqueUserCount(""); r.Err != nil { c.Err = err
c.Err = r.Err
return return
} else {
systemUserCount = r.Data.(int64)
if systemUserCount > int64(*utils.Cfg.AnalyticsSettings.MaxUsersForStatistics) {
l4g.Debug("More than %v users on the system, intensive queries skipped", *utils.Cfg.AnalyticsSettings.MaxUsersForStatistics)
skipIntensiveQueries = true
}
} }
if name == "standard" { if rows == nil {
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 10)
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
rows[2] = &model.AnalyticsRow{"post_count", 0}
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
rows[4] = &model.AnalyticsRow{"team_count", 0}
rows[5] = &model.AnalyticsRow{"total_websocket_connections", 0}
rows[6] = &model.AnalyticsRow{"total_master_db_connections", 0}
rows[7] = &model.AnalyticsRow{"total_read_db_connections", 0}
rows[8] = &model.AnalyticsRow{"daily_active_users", 0}
rows[9] = &model.AnalyticsRow{"monthly_active_users", 0}
openChan := app.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
privateChan := app.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
teamChan := app.Srv.Store.Team().AnalyticsTeamCount()
var userChan store.StoreChannel
if teamId != "" {
userChan = app.Srv.Store.User().AnalyticsUniqueUserCount(teamId)
}
var postChan store.StoreChannel
if !skipIntensiveQueries {
postChan = app.Srv.Store.Post().AnalyticsPostCount(teamId, false, false)
}
dailyActiveChan := app.Srv.Store.User().AnalyticsActiveCount(DAY_MILLISECONDS)
monthlyActiveChan := app.Srv.Store.User().AnalyticsActiveCount(MONTH_MILLISECONDS)
if r := <-openChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[0].Value = float64(r.Data.(int64))
}
if r := <-privateChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[1].Value = float64(r.Data.(int64))
}
if postChan == nil {
rows[2].Value = -1
} else {
if r := <-postChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[2].Value = float64(r.Data.(int64))
}
}
if userChan == nil {
rows[3].Value = float64(systemUserCount)
} else {
if r := <-userChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[3].Value = float64(r.Data.(int64))
}
}
if r := <-teamChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[4].Value = float64(r.Data.(int64))
}
// If in HA mode then aggregrate all the stats
if einterfaces.GetClusterInterface() != nil && *utils.Cfg.ClusterSettings.Enable {
stats, err := einterfaces.GetClusterInterface().GetClusterStats()
if err != nil {
c.Err = err
return
}
totalSockets := app.TotalWebsocketConnections()
totalMasterDb := app.Srv.Store.TotalMasterDbConnections()
totalReadDb := app.Srv.Store.TotalReadDbConnections()
for _, stat := range stats {
totalSockets = totalSockets + stat.TotalWebsocketConnections
totalMasterDb = totalMasterDb + stat.TotalMasterDbConnections
totalReadDb = totalReadDb + stat.TotalReadDbConnections
}
rows[5].Value = float64(totalSockets)
rows[6].Value = float64(totalMasterDb)
rows[7].Value = float64(totalReadDb)
} else {
rows[5].Value = float64(app.TotalWebsocketConnections())
rows[6].Value = float64(app.Srv.Store.TotalMasterDbConnections())
rows[7].Value = float64(app.Srv.Store.TotalReadDbConnections())
}
if r := <-dailyActiveChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[8].Value = float64(r.Data.(int64))
}
if r := <-monthlyActiveChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[9].Value = float64(r.Data.(int64))
}
w.Write([]byte(rows.ToJson()))
} else if name == "post_counts_day" {
if skipIntensiveQueries {
rows := model.AnalyticsRows{&model.AnalyticsRow{"", -1}}
w.Write([]byte(rows.ToJson()))
return
}
if r := <-app.Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
c.Err = r.Err
return
} else {
w.Write([]byte(r.Data.(model.AnalyticsRows).ToJson()))
}
} else if name == "user_counts_with_posts_day" {
if skipIntensiveQueries {
rows := model.AnalyticsRows{&model.AnalyticsRow{"", -1}}
w.Write([]byte(rows.ToJson()))
return
}
if r := <-app.Srv.Store.Post().AnalyticsUserCountsWithPostsByDay(teamId); r.Err != nil {
c.Err = r.Err
return
} else {
w.Write([]byte(r.Data.(model.AnalyticsRows).ToJson()))
}
} else if name == "extra_counts" {
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 6)
rows[0] = &model.AnalyticsRow{"file_post_count", 0}
rows[1] = &model.AnalyticsRow{"hashtag_post_count", 0}
rows[2] = &model.AnalyticsRow{"incoming_webhook_count", 0}
rows[3] = &model.AnalyticsRow{"outgoing_webhook_count", 0}
rows[4] = &model.AnalyticsRow{"command_count", 0}
rows[5] = &model.AnalyticsRow{"session_count", 0}
iHookChan := app.Srv.Store.Webhook().AnalyticsIncomingCount(teamId)
oHookChan := app.Srv.Store.Webhook().AnalyticsOutgoingCount(teamId)
commandChan := app.Srv.Store.Command().AnalyticsCommandCount(teamId)
sessionChan := app.Srv.Store.Session().AnalyticsSessionCount()
var fileChan store.StoreChannel
var hashtagChan store.StoreChannel
if !skipIntensiveQueries {
fileChan = app.Srv.Store.Post().AnalyticsPostCount(teamId, true, false)
hashtagChan = app.Srv.Store.Post().AnalyticsPostCount(teamId, false, true)
}
if fileChan == nil {
rows[0].Value = -1
} else {
if r := <-fileChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[0].Value = float64(r.Data.(int64))
}
}
if hashtagChan == nil {
rows[1].Value = -1
} else {
if r := <-hashtagChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[1].Value = float64(r.Data.(int64))
}
}
if r := <-iHookChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[2].Value = float64(r.Data.(int64))
}
if r := <-oHookChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[3].Value = float64(r.Data.(int64))
}
if r := <-commandChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[4].Value = float64(r.Data.(int64))
}
if r := <-sessionChan; r.Err != nil {
c.Err = r.Err
return
} else {
rows[5].Value = float64(r.Data.(int64))
}
w.Write([]byte(rows.ToJson()))
} else {
c.SetInvalidParam("getAnalytics", "name") c.SetInvalidParam("getAnalytics", "name")
return
} }
w.Write([]byte(rows.ToJson()))
} }
func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -639,40 +262,18 @@ func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
brandInterface := einterfaces.GetBrandInterface() if err := app.SaveBrandImage(imageArray[0]); err != nil {
if brandInterface == nil {
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.not_available.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if err := brandInterface.SaveBrandImage(imageArray[0]); err != nil {
c.Err = err c.Err = err
return return
} }
c.LogAudit("") c.LogAudit("")
rdata := map[string]string{} ReturnStatusOK(w)
rdata["status"] = "OK"
w.Write([]byte(model.MapToJson(rdata)))
} }
func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
if len(utils.Cfg.FileSettings.DriverName) == 0 { if img, err := app.GetBrandImage(); err != nil {
c.Err = model.NewLocAppError("getBrandImage", "api.admin.get_brand_image.storage.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
brandInterface := einterfaces.GetBrandInterface()
if brandInterface == nil {
c.Err = model.NewLocAppError("getBrandImage", "api.admin.get_brand_image.not_available.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if img, err := brandInterface.GetBrandImage(); err != nil {
w.Write(nil) w.Write(nil)
} else { } else {
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
@@ -716,7 +317,7 @@ func adminResetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if err := ResetPassword(c, userId, newPassword); err != nil { if err := app.UpdatePasswordByUserIdSendEmail(userId, newPassword, c.T("api.user.reset_password.method"), c.GetSiteURL()); err != nil {
c.Err = err c.Err = err
return return
} }
@@ -729,15 +330,7 @@ func adminResetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) { func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) {
go func() { app.SyncLdap()
if utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
ldapI.SyncNow()
} else {
l4g.Error("%v", model.NewLocAppError("ldapSyncNow", "ent.ldap.disabled.app_error", nil, "").Error())
}
}
}()
rdata := map[string]string{} rdata := map[string]string{}
rdata["status"] = "ok" rdata["status"] = "ok"
@@ -745,33 +338,18 @@ func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func ldapTest(c *Context, w http.ResponseWriter, r *http.Request) { func ldapTest(c *Context, w http.ResponseWriter, r *http.Request) {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil && utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable { if err := app.TestLdap(); err != nil {
if err := ldapI.RunTest(); err != nil { c.Err = err
c.Err = err
c.Err.StatusCode = 500
}
} else {
c.Err = model.NewLocAppError("ldapTest", "ent.ldap.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
}
if c.Err == nil {
rdata := map[string]string{}
rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata)))
}
}
func samlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
samlInterface := einterfaces.GetSamlInterface()
if samlInterface == nil {
c.Err = model.NewLocAppError("loginWithSaml", "api.admin.saml.not_available.app_error", nil, "")
c.Err.StatusCode = http.StatusFound
return return
} }
if result, err := samlInterface.GetMetadata(); err != nil { rdata := map[string]string{}
rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata)))
}
func samlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
if result, err := app.GetSamlMetadata(); err != nil {
c.Err = model.NewLocAppError("loginWithSaml", "api.admin.saml.metadata.app_error", nil, "err="+err.Message) c.Err = model.NewLocAppError("loginWithSaml", "api.admin.saml.metadata.app_error", nil, "err="+err.Message)
return return
} else { } else {
@@ -805,58 +383,38 @@ func addCertificate(c *Context, w http.ResponseWriter, r *http.Request) {
fileData := fileArray[0] fileData := fileArray[0]
file, err := fileData.Open() if err := app.AddSamlCertificate(fileData); err != nil {
defer file.Close() c.Err = err
if err != nil {
c.Err = model.NewLocAppError("addCertificate", "api.admin.add_certificate.open.app_error", nil, err.Error())
return return
} }
out, err := os.Create(utils.FindDir("config") + fileData.Filename)
if err != nil {
c.Err = model.NewLocAppError("addCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error())
return
}
defer out.Close()
io.Copy(out, file)
ReturnStatusOK(w) ReturnStatusOK(w)
} }
func removeCertificate(c *Context, w http.ResponseWriter, r *http.Request) { func removeCertificate(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
filename := props["filename"] if err := app.RemoveSamlCertificate(props["filename"]); err != nil {
if err := os.Remove(utils.FindConfigFile(filename)); err != nil { c.Err = err
c.Err = model.NewLocAppError("removeCertificate", "api.admin.remove_certificate.delete.app_error",
map[string]interface{}{"Filename": filename}, err.Error())
return return
} }
ReturnStatusOK(w) ReturnStatusOK(w)
} }
func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) { func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) {
status := make(map[string]interface{}) status := app.GetSamlCertificateStatus()
status["IdpCertificateFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.IdpCertificateFile)
status["PrivateKeyFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.PrivateKeyFile)
status["PublicCertificateFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.PublicCertificateFile)
w.Write([]byte(model.StringInterfaceToJson(status))) w.Write([]byte(model.StringInterfaceToJson(status)))
} }
func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) { func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) {
if result := <-app.Srv.Store.User().GetRecentlyActiveUsersForTeam(c.TeamId); result.Err != nil { if profiles, err := app.GetRecentlyActiveUsersForTeam(c.TeamId); err != nil {
c.Err = result.Err c.Err = err
return return
} else { } else {
profiles := result.Data.(map[string]*model.User)
for _, p := range profiles { for _, p := range profiles {
sanitizeProfile(c, p) sanitizeProfile(c, p)
} }
w.Write([]byte(model.UserMapToJson(profiles))) w.Write([]byte(model.UserMapToJson(profiles)))
} }
} }

Просмотреть файл

@@ -4,7 +4,6 @@
package api package api
import ( import (
"io/ioutil"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -144,10 +143,3 @@ func ReturnStatusOK(w http.ResponseWriter) {
m[model.STATUS] = model.STATUS_OK m[model.STATUS] = model.STATUS_OK
w.Write([]byte(model.MapToJson(m))) w.Write([]byte(model.MapToJson(m)))
} }
func closeBody(r *http.Response) {
if r.Body != nil {
ioutil.ReadAll(r.Body)
r.Body.Close()
}
}

Просмотреть файл

@@ -8,11 +8,9 @@ import (
"strconv" "strconv"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/dyatlov/go-opengraph/opengraph"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/platform/app" "github.com/mattermost/platform/app"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
@@ -44,67 +42,28 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetInvalidParam("createPost", "post") c.SetInvalidParam("createPost", "post")
return return
} }
post.UserId = c.Session.UserId
cchan := app.Srv.Store.Channel().Get(post.ChannelId, true) post.UserId = c.Session.UserId
if !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) { if !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) {
c.SetPermissionError(model.PERMISSION_CREATE_POST) c.SetPermissionError(model.PERMISSION_CREATE_POST)
return return
} }
// Check that channel has not been deleted
var channel *model.Channel
if result := <-cchan; result.Err != nil {
c.SetInvalidParam("createPost", "post.channelId")
return
} else {
channel = result.Data.(*model.Channel)
}
if channel.DeleteAt != 0 {
c.Err = model.NewLocAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "")
c.Err.StatusCode = http.StatusBadRequest
return
}
if post.CreateAt != 0 && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { if post.CreateAt != 0 && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
post.CreateAt = 0 post.CreateAt = 0
} }
if rp, err := app.CreatePost(post, c.TeamId, true); err != nil { rp, err := app.CreatePostAsUser(post, c.TeamId)
if err != nil {
c.Err = err c.Err = err
if c.Err.Id == "api.post.create_post.root_id.app_error" ||
c.Err.Id == "api.post.create_post.channel_root_id.app_error" ||
c.Err.Id == "api.post.create_post.parent_id.app_error" {
c.Err.StatusCode = http.StatusBadRequest
}
return return
} else {
// Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app)
if _, ok := post.Props["from_webhook"]; !ok {
if result := <-app.Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, c.Session.UserId); result.Err != nil {
l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, c.Session.UserId, result.Err)
}
}
w.Write([]byte(rp.ToJson()))
} }
w.Write([]byte(rp.ToJson()))
} }
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
if utils.IsLicensed {
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil,
c.T("api.post.update_post.permissions_denied.app_error"))
c.Err.StatusCode = http.StatusForbidden
return
}
}
post := model.PostFromJson(r.Body) post := model.PostFromJson(r.Body)
if post == nil { if post == nil {
@@ -112,77 +71,20 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
pchan := app.Srv.Store.Post().Get(post.Id)
if !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_EDIT_POST) { if !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_EDIT_POST) {
c.SetPermissionError(model.PERMISSION_EDIT_POST) c.SetPermissionError(model.PERMISSION_EDIT_POST)
return return
} }
var oldPost *model.Post post.UserId = c.Session.UserId
if result := <-pchan; result.Err != nil {
c.Err = result.Err rpost, err := app.UpdatePost(post)
if err != nil {
c.Err = err
return return
} else {
oldPost = result.Data.(*model.PostList).Posts[post.Id]
if oldPost == nil {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id)
c.Err.StatusCode = http.StatusBadRequest
return
}
if oldPost.UserId != c.Session.UserId {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil, "oldUserId="+oldPost.UserId)
c.Err.StatusCode = http.StatusForbidden
return
}
if oldPost.DeleteAt != 0 {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil,
c.T("api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}))
c.Err.StatusCode = http.StatusForbidden
return
}
if oldPost.IsSystemMessage() {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id)
c.Err.StatusCode = http.StatusForbidden
return
}
if utils.IsLicensed {
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) {
c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil,
c.T("api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}))
c.Err.StatusCode = http.StatusForbidden
return
}
}
} }
newPost := &model.Post{} w.Write([]byte(rpost.ToJson()))
*newPost = *oldPost
newPost.Message = post.Message
newPost.EditAt = model.GetMillis()
newPost.Hashtags, _ = model.ParseHashtags(post.Message)
if result := <-app.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
c.Err = result.Err
return
} else {
rpost := result.Data.(*model.Post)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
message.Add("post", rpost.ToJson())
go app.Publish(message)
app.InvalidateCacheForChannelPosts(rpost.ChannelId)
w.Write([]byte(rpost.ToJson()))
}
} }
func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) { func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -200,16 +102,12 @@ func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
posts := &model.PostList{} if posts, err := app.GetFlaggedPosts(c.Session.UserId, offset, limit); err != nil {
c.Err = err
if result := <-app.Srv.Store.Post().GetFlaggedPosts(c.Session.UserId, offset, limit); result.Err != nil {
c.Err = result.Err
return return
} else { } else {
posts = result.Data.(*model.PostList) w.Write([]byte(posts.ToJson()))
} }
w.Write([]byte(posts.ToJson()))
} }
func getPosts(c *Context, w http.ResponseWriter, r *http.Request) { func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -233,27 +131,21 @@ func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
etagChan := app.Srv.Store.Post().GetEtag(id, true)
if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_CREATE_POST) { if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_CREATE_POST) {
c.SetPermissionError(model.PERMISSION_CREATE_POST) c.SetPermissionError(model.PERMISSION_CREATE_POST)
return return
} }
etag := (<-etagChan).Data.(string) etag := app.GetPostsEtag(id)
if HandleEtag(etag, "Get Posts", w, r) { if HandleEtag(etag, "Get Posts", w, r) {
return return
} }
pchan := app.Srv.Store.Post().GetPosts(id, offset, limit, true) if list, err := app.GetPosts(id, offset, limit); err != nil {
c.Err = err
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return return
} else { } else {
list := result.Data.(*model.PostList)
w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(list.ToJson())) w.Write([]byte(list.ToJson()))
} }
@@ -275,19 +167,15 @@ func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
pchan := app.Srv.Store.Post().GetPostsSince(id, time, true)
if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) { if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return
} }
if result := <-pchan; result.Err != nil { if list, err := app.GetPostsSince(id, time); err != nil {
c.Err = result.Err c.Err = err
return return
} else { } else {
list := result.Data.(*model.PostList)
w.Write([]byte(list.ToJson())) w.Write([]byte(list.ToJson()))
} }
@@ -308,21 +196,17 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
pchan := app.Srv.Store.Post().Get(postId)
if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return
} }
if result := <-pchan; result.Err != nil { if list, err := app.GetPostThread(postId); err != nil {
c.Err = result.Err c.Err = err
return return
} else if HandleEtag(result.Data.(*model.PostList).Etag(), "Get Post", w, r) { } else if HandleEtag(list.Etag(), "Get Post", w, r) {
return return
} else { } else {
list := result.Data.(*model.PostList)
if !list.IsChannelId(channelId) { if !list.IsChannelId(channelId) {
c.Err = model.NewLocAppError("getPost", "api.post.get_post.permissions.app_error", nil, "") c.Err = model.NewLocAppError("getPost", "api.post.get_post.permissions.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
@@ -343,12 +227,10 @@ func getPostById(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if result := <-app.Srv.Store.Post().Get(postId); result.Err != nil { if list, err := app.GetPostThread(postId); err != nil {
c.Err = result.Err c.Err = err
return return
} else { } else {
list := result.Data.(*model.PostList)
if len(list.Order) != 1 { if len(list.Order) != 1 {
c.Err = model.NewLocAppError("getPostById", "api.post_get_post_by_id.get.app_error", nil, "") c.Err = model.NewLocAppError("getPostById", "api.post_get_post_by_id.get.app_error", nil, "")
return return
@@ -378,39 +260,17 @@ func getPermalinkTmp(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if result := <-app.Srv.Store.Post().Get(postId); result.Err != nil { if !app.HasPermissionToChannelByPost(c.Session.UserId, postId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) {
c.Err = result.Err c.SetPermissionError(model.PERMISSION_JOIN_PUBLIC_CHANNELS)
return
}
if list, err := app.GetPermalinkPost(postId, c.Session.UserId); err != nil {
c.Err = err
return
} else if HandleEtag(list.Etag(), "Get Permalink TMP", w, r) {
return return
} else { } else {
list := result.Data.(*model.PostList)
if len(list.Order) != 1 {
c.Err = model.NewLocAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "")
return
}
post := list.Posts[list.Order[0]]
var channel *model.Channel
var err *model.AppError
if channel, err = app.GetChannel(post.ChannelId); err != nil {
c.Err = err
return
}
if !app.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) {
c.SetPermissionError(model.PERMISSION_JOIN_PUBLIC_CHANNELS)
return
}
if err = app.JoinChannel(channel, c.Session.UserId); err != nil {
c.Err = err
return
}
if HandleEtag(list.Etag(), "Get Permalink TMP", w, r) {
return
}
w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag()) w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag())
w.Write([]byte(list.ToJson())) w.Write([]byte(list.ToJson()))
} }
@@ -436,69 +296,27 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
pchan := app.Srv.Store.Post().Get(postId) if !app.SessionHasPermissionToPost(c.Session, postId, model.PERMISSION_DELETE_OTHERS_POSTS) {
c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS)
return
}
if result := <-pchan; result.Err != nil { if post, err := app.DeletePost(postId); err != nil {
c.Err = result.Err c.Err = err
return return
} else { } else {
post := result.Data.(*model.PostList).Posts[postId]
if post == nil {
c.SetInvalidParam("deletePost", "postId")
return
}
if post.ChannelId != channelId { if post.ChannelId != channelId {
c.Err = model.NewLocAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "") c.Err = model.NewLocAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
return return
} }
if post.UserId != c.Session.UserId && !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_DELETE_OTHERS_POSTS) {
c.Err = model.NewLocAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
if dresult := <-app.Srv.Store.Post().Delete(postId, model.GetMillis()); dresult.Err != nil {
c.Err = dresult.Err
return
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
message.Add("post", post.ToJson())
go app.Publish(message)
go DeletePostFiles(post)
go DeleteFlaggedPost(c.Session.UserId, post)
app.InvalidateCacheForChannelPosts(post.ChannelId)
result := make(map[string]string) result := make(map[string]string)
result["id"] = postId result["id"] = postId
w.Write([]byte(model.MapToJson(result))) w.Write([]byte(model.MapToJson(result)))
} }
} }
func DeleteFlaggedPost(userId string, post *model.Post) {
if result := <-app.Srv.Store.Preference().Delete(userId, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id); result.Err != nil {
l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err)
return
}
}
func DeletePostFiles(post *model.Post) {
if len(post.FileIds) != 0 {
return
}
if result := <-app.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil {
l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err)
}
}
func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) { func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) {
getPostsBeforeOrAfter(c, w, r, true) getPostsBeforeOrAfter(c, w, r, true)
} }
@@ -534,32 +352,22 @@ func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, b
return return
} }
// We can do better than this etag in this situation
etagChan := app.Srv.Store.Post().GetEtag(id, true)
if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) { if !app.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return
} }
etag := (<-etagChan).Data.(string) // We can do better than this etag in this situation
etag := app.GetPostsEtag(id)
if HandleEtag(etag, "Get Posts Before or After", w, r) { if HandleEtag(etag, "Get Posts Before or After", w, r) {
return return
} }
var pchan store.StoreChannel if list, err := app.GetPostsAroundPost(postId, id, offset, numPosts, before); err != nil {
if before { c.Err = err
pchan = app.Srv.Store.Post().GetPostsBefore(id, postId, numPosts, offset)
} else {
pchan = app.Srv.Store.Post().GetPostsAfter(id, postId, numPosts, offset)
}
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return return
} else { } else {
list := result.Data.(*model.PostList)
w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(list.ToJson())) w.Write([]byte(list.ToJson()))
} }
@@ -579,26 +387,10 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
isOrSearch = val.(bool) isOrSearch = val.(bool)
} }
paramsList := model.ParseSearchParams(terms) posts, err := app.SearchPostsInTeam(terms, c.Session.UserId, c.TeamId, isOrSearch)
channels := []store.StoreChannel{} if err != nil {
c.Err = err
for _, params := range paramsList { return
params.OrTerms = isOrSearch
// don't allow users to search for everything
if params.Terms != "*" {
channels = append(channels, app.Srv.Store.Post().Search(c.TeamId, c.Session.UserId, params))
}
}
posts := &model.PostList{}
for _, channel := range channels {
if result := <-channel; result.Err != nil {
c.Err = result.Err
return
} else {
data := result.Data.(*model.PostList)
posts.Extend(data)
}
} }
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
@@ -620,69 +412,35 @@ func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
pchan := app.Srv.Store.Post().Get(postId)
fchan := app.Srv.Store.FileInfo().GetForPost(postId)
if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return
} }
var infos []*model.FileInfo if infos, err := app.GetFileInfosForPost(postId); err != nil {
if result := <-fchan; result.Err != nil { c.Err = err
c.Err = result.Err
return return
} else { } else if HandleEtag(model.GetEtagForFileInfos(infos), "Get File Infos For Post", w, r) {
infos = result.Data.([]*model.FileInfo)
}
if len(infos) == 0 {
// No FileInfos were returned so check if they need to be created for this post
var post *model.Post
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return
} else {
post = result.Data.(*model.PostList).Posts[postId]
}
if len(post.Filenames) > 0 {
// The post has Filenames that need to be replaced with FileInfos
infos = app.MigrateFilenamesToFileInfos(post)
}
}
etag := model.GetEtagForFileInfos(infos)
if HandleEtag(etag, "Get File Infos For Post", w, r) {
return return
} else { } else {
w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Header().Set(model.HEADER_ETAG_SERVER, model.GetEtagForFileInfos(infos))
w.Write([]byte(model.FileInfosToJson(infos))) w.Write([]byte(model.FileInfosToJson(infos)))
} }
} }
func getOpenGraphMetadata(c *Context, w http.ResponseWriter, r *http.Request) { func getOpenGraphMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.StringInterfaceFromJson(r.Body) props := model.StringInterfaceFromJson(r.Body)
og := opengraph.NewOpenGraph()
res, err := http.Get(props["url"].(string)) url := ""
defer closeBody(res) ok := false
if err != nil { if url, ok = props["url"].(string); len(url) == 0 || !ok {
writeOpenGraphToResponse(w, og) c.SetInvalidParam("getOpenGraphMetadata", "url")
return return
} }
if err := og.ProcessHTML(res.Body); err != nil { og := app.GetOpenGraphMetadata(url)
writeOpenGraphToResponse(w, og)
return
}
writeOpenGraphToResponse(w, og)
}
func writeOpenGraphToResponse(w http.ResponseWriter, og *opengraph.OpenGraph) {
ogJson, err := og.ToJSON() ogJson, err := og.ToJSON()
if err != nil { if err != nil {
w.Write([]byte(`{"url": ""}`)) w.Write([]byte(`{"url": ""}`))

Просмотреть файл

@@ -7,10 +7,8 @@ import (
"bytes" "bytes"
b64 "encoding/base64" b64 "encoding/base64"
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -94,16 +92,9 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
user.EmailVerified = false user.EmailVerified = false
shouldSendWelcomeEmail := true
hash := r.URL.Query().Get("h") hash := r.URL.Query().Get("h")
inviteId := r.URL.Query().Get("iid") inviteId := r.URL.Query().Get("iid")
if !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) {
c.Err = model.NewLocAppError("createUser", "api.user.create_user.accepted_domain.app_error", nil, "")
return
}
var ruser *model.User var ruser *model.User
var err *model.AppError var err *model.AppError
if len(hash) > 0 { if len(hash) > 0 {
@@ -113,10 +104,8 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = err c.Err = err
return return
} }
shouldSendWelcomeEmail = false
} else if len(inviteId) > 0 { } else if len(inviteId) > 0 {
ruser, err = app.CreateUserWithInviteId(user, inviteId) ruser, err = app.CreateUserWithInviteId(user, inviteId, c.GetSiteURL())
if err != nil { if err != nil {
c.Err = err c.Err = err
return return
@@ -132,9 +121,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = err c.Err = err
return return
} }
}
if shouldSendWelcomeEmail {
if err := app.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, c.GetSiteURL()); err != nil { if err := app.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, c.GetSiteURL()); err != nil {
l4g.Error(err.Error()) l4g.Error(err.Error())
} }
@@ -144,49 +131,6 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
} }
// Check that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
if len(domains) == 0 {
return true
}
domainArray := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
matched := false
for _, d := range domainArray {
if strings.HasSuffix(strings.ToLower(user.Email), "@"+d) {
matched = true
break
}
}
return matched
}
func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool {
shouldVerifyHash := true
if team.Type == model.TEAM_INVITE && len(team.AllowedDomains) > 0 && len(hash) == 0 && user != nil {
matched := CheckUserDomain(user, team.AllowedDomains)
if matched {
shouldVerifyHash = false
} else {
return true
}
}
if team.Type == model.TEAM_OPEN {
shouldVerifyHash = false
}
if len(hash) > 0 {
shouldVerifyHash = true
}
return shouldVerifyHash
}
func login(c *Context, w http.ResponseWriter, r *http.Request) { func login(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
@@ -594,10 +538,7 @@ func getByEmail(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r) params := mux.Vars(r)
email := params["email"] email := params["email"]
var user *model.User if user, err := app.GetUserByEmail(email); err != nil {
var err *model.AppError
if user, err = app.GetUserByEmail(email); err != nil {
c.Err = err c.Err = err
return return
} else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Email", w, r) { } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Email", w, r) {
@@ -631,11 +572,8 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
var profiles map[string]*model.User if profiles, err := app.GetUsers(offset, limit); err != nil {
var profileErr *model.AppError c.Err = err
if profiles, profileErr = app.GetUsers(offset, limit); profileErr != nil {
c.Err = profileErr
return return
} else { } else {
for k, p := range profiles { for k, p := range profiles {
@@ -674,11 +612,8 @@ func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
var profiles map[string]*model.User if profiles, err := app.GetUsersInTeam(teamId, offset, limit); err != nil {
var profileErr *model.AppError c.Err = err
if profiles, profileErr = app.GetUsersInTeam(teamId, offset, limit); profileErr != nil {
c.Err = profileErr
return return
} else { } else {
for k, p := range profiles { for k, p := range profiles {
@@ -694,18 +629,6 @@ func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r) params := mux.Vars(r)
channelId := params["channel_id"] channelId := params["channel_id"]
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
}
if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
offset, err := strconv.Atoi(params["offset"]) offset, err := strconv.Atoi(params["offset"])
if err != nil { if err != nil {
c.SetInvalidParam("getProfiles", "offset") c.SetInvalidParam("getProfiles", "offset")
@@ -718,6 +641,18 @@ func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
}
if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
var profiles map[string]*model.User var profiles map[string]*model.User
var profileErr *model.AppError var profileErr *model.AppError
@@ -761,11 +696,8 @@ func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request)
return return
} }
var profiles map[string]*model.User if profiles, err := app.GetUsersNotInChannel(c.TeamId, channelId, offset, limit); err != nil {
var profileErr *model.AppError c.Err = err
if profiles, err = app.GetUsersNotInChannel(c.TeamId, channelId, offset, limit); profileErr != nil {
c.Err = profileErr
return return
} else { } else {
for k, p := range profiles { for k, p := range profiles {
@@ -897,11 +829,6 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if err := utils.IsPasswordValid(user.Password); user.Password != "" && err != nil {
c.Err = err
return
}
if ruser, err := app.UpdateUser(user, c.GetSiteURL()); err != nil { if ruser, err := app.UpdateUser(user, c.GetSiteURL()); err != nil {
c.Err = err c.Err = err
return return
@@ -942,11 +869,6 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
newPassword := props["new_password"] newPassword := props["new_password"]
if err := utils.IsPasswordValid(newPassword); err != nil {
c.Err = err
return
}
if userId != c.Session.UserId { if userId != c.Session.UserId {
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.context.app_error", nil, "") c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.context.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
@@ -984,7 +906,7 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if err := app.UpdatePasswordSendEmail(user, model.HashPassword(newPassword), c.T("api.user.update_password.menu"), c.GetSiteURL()); err != nil { if err := app.UpdatePasswordSendEmail(user, newPassword, c.T("api.user.update_password.menu"), c.GetSiteURL()); err != nil {
c.Err = err c.Err = err
return return
} else { } else {
@@ -1039,13 +961,6 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
active := props["active"] == "true" active := props["active"] == "true"
var user *model.User
var err *model.AppError
if user, err = app.GetUser(userId); err != nil {
c.Err = err
return
}
// true when you're trying to de-activate yourself // true when you're trying to de-activate yourself
isSelfDeactive := !active && userId == c.Session.UserId isSelfDeactive := !active && userId == c.Session.UserId
@@ -1055,13 +970,7 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if user.IsLDAPUser() { if ruser, err := app.UpdateActiveNoLdap(userId, active); err != nil {
c.Err = model.NewLocAppError("updateActive", "api.user.update_active.no_deactivate_ldap.app_error", nil, "userId="+userId)
c.Err.StatusCode = http.StatusBadRequest
return
}
if ruser, err := app.UpdateActive(user, active); err != nil {
c.Err = err c.Err = err
} else { } else {
c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active)) c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active))
@@ -1078,42 +987,13 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
var user *model.User if sent, err := app.SendPasswordReset(email, c.GetSiteURL()); err != nil {
var err *model.AppError
if user, err = app.GetUserByEmail(email); err != nil {
w.Write([]byte(model.MapToJson(props)))
return
}
if user.AuthData != nil && len(*user.AuthData) != 0 {
c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id)
return
}
var recovery *model.PasswordRecovery
if recovery, err = app.CreatePasswordRecovery(user.Id); err != nil {
c.Err = err c.Err = err
return return
} else if sent {
c.LogAudit("sent=" + email)
} }
link := fmt.Sprintf("%s/reset_password_complete?code=%s", c.GetSiteURL(), url.QueryEscape(recovery.Code))
subject := c.T("api.templates.reset_subject")
bodyPage := utils.NewHTMLTemplate("reset_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["Title"] = c.T("api.templates.reset_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.reset_body.info"))
bodyPage.Props["ResetUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.reset_body.button")
if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
return
}
c.LogAuditWithUserId(user.Id, "sent="+email)
w.Write([]byte(model.MapToJson(props))) w.Write([]byte(model.MapToJson(props)))
} }
@@ -1127,64 +1007,22 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
} }
newPassword := props["new_password"] newPassword := props["new_password"]
if err := utils.IsPasswordValid(newPassword); err != nil {
c.LogAudit("attempt - code=" + code)
if err := app.ResetPasswordFromCode(code, newPassword, c.GetSiteURL()); err != nil {
c.LogAudit("fail - code=" + code)
c.Err = err c.Err = err
return return
} }
c.LogAudit("attempt") c.LogAudit("success - code=" + code)
userId := ""
if recovery, err := app.GetPasswordRecovery(code); err != nil {
c.LogAuditWithUserId(userId, "fail - bad code")
c.Err = err
return
} else {
if model.GetMillis()-recovery.CreateAt < model.PASSWORD_RECOVER_EXPIRY_TIME {
userId = recovery.UserId
} else {
c.LogAuditWithUserId(userId, "fail - link expired")
c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "")
return
}
if err := app.DeletePasswordRecoveryForUser(userId); err != nil {
l4g.Error(err.Error())
}
}
if err := ResetPassword(c, userId, newPassword); err != nil {
c.Err = err
return
}
c.LogAuditWithUserId(userId, "success")
rdata := map[string]string{} rdata := map[string]string{}
rdata["status"] = "ok" rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata))) w.Write([]byte(model.MapToJson(rdata)))
} }
func ResetPassword(c *Context, userId, newPassword string) *model.AppError {
var user *model.User
var err *model.AppError
if user, err = app.GetUser(userId); err != nil {
return err
}
if user.AuthData != nil && len(*user.AuthData) != 0 && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
return model.NewLocAppError("ResetPassword", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id)
}
if err := app.UpdatePasswordSendEmail(user, model.HashPassword(newPassword), c.T("api.user.reset_password.method"), c.GetSiteURL()); err != nil {
return err
}
return nil
}
func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
@@ -1225,22 +1063,13 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
var user *model.User ruser, err := app.UpdateUserNotifyProps(userId, props, c.GetSiteURL())
var err *model.AppError if err != nil {
if user, err = app.GetUser(userId); err != nil {
c.Err = err c.Err = err
return return
} }
user.NotifyProps = props c.LogAuditWithUserId(ruser.Id, "")
var ruser *model.User
if ruser, err = app.UpdateUser(user, c.GetSiteURL()); err != nil {
c.Err = err
return
}
c.LogAuditWithUserId(user.Id, "")
options := utils.Cfg.GetSanitizeOptions() options := utils.Cfg.GetSanitizeOptions()
options["passwordupdate"] = false options["passwordupdate"] = false
@@ -1340,7 +1169,7 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if err := app.UpdatePassword(user, model.HashPassword(password)); err != nil { if err := app.UpdatePassword(user, password); err != nil {
c.LogAudit("fail - database issue") c.LogAudit("fail - database issue")
c.Err = err c.Err = err
return return
@@ -1509,7 +1338,7 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if err := app.UpdatePassword(user, model.HashPassword(emailPassword)); err != nil { if err := app.UpdatePassword(user, emailPassword); err != nil {
c.LogAudit("fail - database issue") c.LogAudit("fail - database issue")
c.Err = err c.Err = err
return return

Просмотреть файл

@@ -83,32 +83,6 @@ func TestCreateUser(t *testing.T) {
} }
} }
func TestCheckUserDomain(t *testing.T) {
th := Setup().InitBasic()
user := th.BasicUser
cases := []struct {
domains string
matched bool
}{
{"simulator.amazonses.com", true},
{"gmail.com", false},
{"", true},
{"gmail.com simulator.amazonses.com", true},
}
for _, c := range cases {
matched := CheckUserDomain(user, c.domains)
if matched != c.matched {
if c.matched {
t.Logf("'%v' should have matched '%v'", user.Email, c.domains)
} else {
t.Logf("'%v' should not have matched '%v'", user.Email, c.domains)
}
t.FailNow()
}
}
}
func TestLogin(t *testing.T) { func TestLogin(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
Client := th.BasicClient Client := th.BasicClient
@@ -1356,6 +1330,7 @@ func TestResetPassword(t *testing.T) {
} }
if _, err := Client.ResetPassword(recovery.Code, "newpwd1"); err != nil { if _, err := Client.ResetPassword(recovery.Code, "newpwd1"); err != nil {
t.Log(recovery.Code)
t.Fatal(err) t.Fatal(err)
} }

Просмотреть файл

@@ -89,7 +89,7 @@ func getWebrtcToken(sessionId string) (string, *model.AppError) {
if rp, err := httpClient.Do(rq); err != nil { if rp, err := httpClient.Do(rq); err != nil {
return "", model.NewLocAppError("WebRTC.Token", "model.client.connecting.app_error", nil, err.Error()) return "", model.NewLocAppError("WebRTC.Token", "model.client.connecting.app_error", nil, err.Error())
} else if rp.StatusCode >= 300 { } else if rp.StatusCode >= 300 {
defer closeBody(rp) defer app.CloseBody(rp)
return "", model.AppErrorFromJson(rp.Body) return "", model.AppErrorFromJson(rp.Body)
} else { } else {
janusResponse := model.GatewayResponseFromJson(rp.Body) janusResponse := model.GatewayResponseFromJson(rp.Body)

182
app/admin.go Обычный файл
Просмотреть файл

@@ -0,0 +1,182 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"bufio"
"os"
"strings"
"time"
"runtime/debug"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
func GetLogs() ([]string, *model.AppError) {
var lines []string
if utils.Cfg.LogSettings.EnableFile {
file, err := os.Open(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation))
if err != nil {
return nil, model.NewLocAppError("getLogs", "api.admin.file_read_error", nil, err.Error())
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
} else {
lines = append(lines, "")
}
if einterfaces.GetClusterInterface() != nil {
clines, err := einterfaces.GetClusterInterface().GetLogs()
if err != nil {
return nil, err
}
lines = append(lines, clines...)
}
return lines, nil
}
func GetClusterStatus() []*model.ClusterInfo {
infos := make([]*model.ClusterInfo, 0)
if einterfaces.GetClusterInterface() != nil {
infos = einterfaces.GetClusterInterface().GetClusterInfos()
}
return infos
}
func InvalidateAllCaches() *model.AppError {
debug.FreeOSMemory()
InvalidateAllCachesSkipSend()
if einterfaces.GetClusterInterface() != nil {
err := einterfaces.GetClusterInterface().InvalidateAllCaches()
if err != nil {
return err
}
}
return nil
}
func InvalidateAllCachesSkipSend() {
l4g.Info(utils.T("api.context.invalidate_all_caches"))
sessionCache.Purge()
ClearStatusCache()
store.ClearChannelCaches()
store.ClearUserCaches()
store.ClearPostCaches()
}
func GetConfig() *model.Config {
json := utils.Cfg.ToJson()
cfg := model.ConfigFromJson(strings.NewReader(json))
cfg.Sanitize()
return cfg
}
func ReloadConfig() {
debug.FreeOSMemory()
utils.LoadConfig(utils.CfgFileName)
// start/restart email batching job if necessary
InitEmailBatching()
}
func SaveConfig(cfg *model.Config) *model.AppError {
cfg.SetDefaults()
utils.Desanitize(cfg)
if err := cfg.IsValid(); err != nil {
return err
}
if err := utils.ValidateLdapFilter(cfg); err != nil {
return err
}
if *utils.Cfg.ClusterSettings.Enable {
return model.NewLocAppError("saveConfig", "ent.cluster.save_config.error", nil, "")
}
//oldCfg := utils.Cfg
utils.SaveConfig(utils.CfgFileName, cfg)
utils.LoadConfig(utils.CfgFileName)
if einterfaces.GetMetricsInterface() != nil {
if *utils.Cfg.MetricsSettings.Enable {
einterfaces.GetMetricsInterface().StartServer()
} else {
einterfaces.GetMetricsInterface().StopServer()
}
}
// Future feature is to sync the configuration files
// if einterfaces.GetClusterInterface() != nil {
// err := einterfaces.GetClusterInterface().ConfigChanged(cfg, oldCfg, true)
// if err != nil {
// return err
// }
// }
// start/restart email batching job if necessary
InitEmailBatching()
return nil
}
func RecycleDatabaseConnection() {
oldStore := Srv.Store
l4g.Warn(utils.T("api.admin.recycle_db_start.warn"))
Srv.Store = store.NewSqlStore()
time.Sleep(20 * time.Second)
oldStore.Close()
l4g.Warn(utils.T("api.admin.recycle_db_end.warn"))
}
func TestEmail(userId string, cfg *model.Config) *model.AppError {
if len(cfg.EmailSettings.SMTPServer) == 0 {
return model.NewLocAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}))
}
// if the user hasn't changed their email settings, fill in the actual SMTP password so that
// the user can verify an existing SMTP connection
if cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
if cfg.EmailSettings.SMTPServer == utils.Cfg.EmailSettings.SMTPServer &&
cfg.EmailSettings.SMTPPort == utils.Cfg.EmailSettings.SMTPPort &&
cfg.EmailSettings.SMTPUsername == utils.Cfg.EmailSettings.SMTPUsername {
cfg.EmailSettings.SMTPPassword = utils.Cfg.EmailSettings.SMTPPassword
} else {
return model.NewLocAppError("testEmail", "api.admin.test_email.reenter_password", nil, "")
}
}
if user, err := GetUser(userId); err != nil {
return err
} else {
T := utils.GetUserTranslations(user.Locale)
if err := utils.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg); err != nil {
return err
}
}
return nil
}

239
app/analytics.go Обычный файл
Просмотреть файл

@@ -0,0 +1,239 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
const (
DAY_MILLISECONDS = 24 * 60 * 60 * 1000
MONTH_MILLISECONDS = 31 * DAY_MILLISECONDS
)
func GetAnalytics(name string, teamId string) (model.AnalyticsRows, *model.AppError) {
skipIntensiveQueries := false
var systemUserCount int64
if r := <-Srv.Store.User().AnalyticsUniqueUserCount(""); r.Err != nil {
return nil, r.Err
} else {
systemUserCount = r.Data.(int64)
if systemUserCount > int64(*utils.Cfg.AnalyticsSettings.MaxUsersForStatistics) {
l4g.Debug("More than %v users on the system, intensive queries skipped", *utils.Cfg.AnalyticsSettings.MaxUsersForStatistics)
skipIntensiveQueries = true
}
}
if name == "standard" {
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 10)
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
rows[2] = &model.AnalyticsRow{"post_count", 0}
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
rows[4] = &model.AnalyticsRow{"team_count", 0}
rows[5] = &model.AnalyticsRow{"total_websocket_connections", 0}
rows[6] = &model.AnalyticsRow{"total_master_db_connections", 0}
rows[7] = &model.AnalyticsRow{"total_read_db_connections", 0}
rows[8] = &model.AnalyticsRow{"daily_active_users", 0}
rows[9] = &model.AnalyticsRow{"monthly_active_users", 0}
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
teamChan := Srv.Store.Team().AnalyticsTeamCount()
var userChan store.StoreChannel
if teamId != "" {
userChan = Srv.Store.User().AnalyticsUniqueUserCount(teamId)
}
var postChan store.StoreChannel
if !skipIntensiveQueries {
postChan = Srv.Store.Post().AnalyticsPostCount(teamId, false, false)
}
dailyActiveChan := Srv.Store.User().AnalyticsActiveCount(DAY_MILLISECONDS)
monthlyActiveChan := Srv.Store.User().AnalyticsActiveCount(MONTH_MILLISECONDS)
if r := <-openChan; r.Err != nil {
return nil, r.Err
} else {
rows[0].Value = float64(r.Data.(int64))
}
if r := <-privateChan; r.Err != nil {
return nil, r.Err
} else {
rows[1].Value = float64(r.Data.(int64))
}
if postChan == nil {
rows[2].Value = -1
} else {
if r := <-postChan; r.Err != nil {
return nil, r.Err
} else {
rows[2].Value = float64(r.Data.(int64))
}
}
if userChan == nil {
rows[3].Value = float64(systemUserCount)
} else {
if r := <-userChan; r.Err != nil {
return nil, r.Err
} else {
rows[3].Value = float64(r.Data.(int64))
}
}
if r := <-teamChan; r.Err != nil {
return nil, r.Err
} else {
rows[4].Value = float64(r.Data.(int64))
}
// If in HA mode then aggregrate all the stats
if einterfaces.GetClusterInterface() != nil && *utils.Cfg.ClusterSettings.Enable {
stats, err := einterfaces.GetClusterInterface().GetClusterStats()
if err != nil {
return nil, err
}
totalSockets := TotalWebsocketConnections()
totalMasterDb := Srv.Store.TotalMasterDbConnections()
totalReadDb := Srv.Store.TotalReadDbConnections()
for _, stat := range stats {
totalSockets = totalSockets + stat.TotalWebsocketConnections
totalMasterDb = totalMasterDb + stat.TotalMasterDbConnections
totalReadDb = totalReadDb + stat.TotalReadDbConnections
}
rows[5].Value = float64(totalSockets)
rows[6].Value = float64(totalMasterDb)
rows[7].Value = float64(totalReadDb)
} else {
rows[5].Value = float64(TotalWebsocketConnections())
rows[6].Value = float64(Srv.Store.TotalMasterDbConnections())
rows[7].Value = float64(Srv.Store.TotalReadDbConnections())
}
if r := <-dailyActiveChan; r.Err != nil {
return nil, r.Err
} else {
rows[8].Value = float64(r.Data.(int64))
}
if r := <-monthlyActiveChan; r.Err != nil {
return nil, r.Err
} else {
rows[9].Value = float64(r.Data.(int64))
}
return rows, nil
} else if name == "post_counts_day" {
if skipIntensiveQueries {
rows := model.AnalyticsRows{&model.AnalyticsRow{"", -1}}
return rows, nil
}
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
return nil, r.Err
} else {
return r.Data.(model.AnalyticsRows), nil
}
} else if name == "user_counts_with_posts_day" {
if skipIntensiveQueries {
rows := model.AnalyticsRows{&model.AnalyticsRow{"", -1}}
return rows, nil
}
if r := <-Srv.Store.Post().AnalyticsUserCountsWithPostsByDay(teamId); r.Err != nil {
return nil, r.Err
} else {
return r.Data.(model.AnalyticsRows), nil
}
} else if name == "extra_counts" {
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 6)
rows[0] = &model.AnalyticsRow{"file_post_count", 0}
rows[1] = &model.AnalyticsRow{"hashtag_post_count", 0}
rows[2] = &model.AnalyticsRow{"incoming_webhook_count", 0}
rows[3] = &model.AnalyticsRow{"outgoing_webhook_count", 0}
rows[4] = &model.AnalyticsRow{"command_count", 0}
rows[5] = &model.AnalyticsRow{"session_count", 0}
iHookChan := Srv.Store.Webhook().AnalyticsIncomingCount(teamId)
oHookChan := Srv.Store.Webhook().AnalyticsOutgoingCount(teamId)
commandChan := Srv.Store.Command().AnalyticsCommandCount(teamId)
sessionChan := Srv.Store.Session().AnalyticsSessionCount()
var fileChan store.StoreChannel
var hashtagChan store.StoreChannel
if !skipIntensiveQueries {
fileChan = Srv.Store.Post().AnalyticsPostCount(teamId, true, false)
hashtagChan = Srv.Store.Post().AnalyticsPostCount(teamId, false, true)
}
if fileChan == nil {
rows[0].Value = -1
} else {
if r := <-fileChan; r.Err != nil {
return nil, r.Err
} else {
rows[0].Value = float64(r.Data.(int64))
}
}
if hashtagChan == nil {
rows[1].Value = -1
} else {
if r := <-hashtagChan; r.Err != nil {
return nil, r.Err
} else {
rows[1].Value = float64(r.Data.(int64))
}
}
if r := <-iHookChan; r.Err != nil {
return nil, r.Err
} else {
rows[2].Value = float64(r.Data.(int64))
}
if r := <-oHookChan; r.Err != nil {
return nil, r.Err
} else {
rows[3].Value = float64(r.Data.(int64))
}
if r := <-commandChan; r.Err != nil {
return nil, r.Err
} else {
rows[4].Value = float64(r.Data.(int64))
}
if r := <-sessionChan; r.Err != nil {
return nil, r.Err
} else {
rows[5].Value = float64(r.Data.(int64))
}
return rows, nil
}
return nil, nil
}
func GetRecentlyActiveUsersForTeam(teamId string) (map[string]*model.User, *model.AppError) {
if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(teamId); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(map[string]*model.User), nil
}
}

16
app/app.go Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"io/ioutil"
"net/http"
)
func CloseBody(r *http.Response) {
if r.Body != nil {
ioutil.ReadAll(r.Body)
r.Body.Close()
}
}

Просмотреть файл

@@ -83,6 +83,19 @@ func SessionHasPermissionToUser(session model.Session, userId string) bool {
return false return false
} }
func SessionHasPermissionToPost(session model.Session, postId string, permission *model.Permission) bool {
post, err := GetSinglePost(postId)
if err != nil {
return false
}
if post.UserId == session.UserId {
return true
}
return SessionHasPermissionToChannel(session, post.ChannelId, permission)
}
func HasPermissionTo(askingUserId string, permission *model.Permission) bool { func HasPermissionTo(askingUserId string, permission *model.Permission) bool {
user, err := GetUser(askingUserId) user, err := GetUser(askingUserId)
if err != nil { if err != nil {
@@ -135,6 +148,24 @@ func HasPermissionToChannel(askingUserId string, channelId string, permission *m
return HasPermissionTo(askingUserId, permission) return HasPermissionTo(askingUserId, permission)
} }
func HasPermissionToChannelByPost(askingUserId string, postId string, permission *model.Permission) bool {
var channelMember *model.ChannelMember
if result := <-Srv.Store.Channel().GetMemberForPost(postId, askingUserId); result.Err == nil {
channelMember = result.Data.(*model.ChannelMember)
if CheckIfRolesGrantPermission(channelMember.GetRoles(), permission.Id) {
return true
}
}
if result := <-Srv.Store.Channel().GetForPost(postId); result.Err == nil {
channel := result.Data.(*model.Channel)
return HasPermissionToTeam(askingUserId, channel.TeamId, permission)
}
return HasPermissionTo(askingUserId, permission)
}
func HasPermissionToUser(askingUserId string, userId string) bool { func HasPermissionToUser(askingUserId string, userId string) bool {
if askingUserId == userId { if askingUserId == userId {
return true return true

49
app/brand.go Обычный файл
Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"mime/multipart"
"net/http"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func SaveBrandImage(imageData *multipart.FileHeader) *model.AppError {
brandInterface := einterfaces.GetBrandInterface()
if brandInterface == nil {
err := model.NewLocAppError("SaveBrandImage", "api.admin.upload_brand_image.not_available.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return err
}
if err := brandInterface.SaveBrandImage(imageData); err != nil {
return err
}
return nil
}
func GetBrandImage() ([]byte, *model.AppError) {
if len(utils.Cfg.FileSettings.DriverName) == 0 {
err := model.NewLocAppError("GetBrandImage", "api.admin.get_brand_image.storage.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return nil, err
}
brandInterface := einterfaces.GetBrandInterface()
if brandInterface == nil {
err := model.NewLocAppError("GetBrandImage", "api.admin.get_brand_image.not_available.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return nil, err
}
if img, err := brandInterface.GetBrandImage(); err != nil {
return nil, err
} else {
return img, nil
}
}

62
app/compliance.go Обычный файл
Просмотреть файл

@@ -0,0 +1,62 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"io/ioutil"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func GetComplianceReports() (model.Compliances, *model.AppError) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance {
return nil, model.NewLocAppError("GetComplianceReports", "ent.compliance.licence_disable.app_error", nil, "")
}
if result := <-Srv.Store.Compliance().GetAll(); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(model.Compliances), nil
}
}
func SaveComplianceReport(job *model.Compliance) (*model.Compliance, *model.AppError) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance || einterfaces.GetComplianceInterface() == nil {
return nil, model.NewLocAppError("saveComplianceReport", "ent.compliance.licence_disable.app_error", nil, "")
}
job.Type = model.COMPLIANCE_TYPE_ADHOC
if result := <-Srv.Store.Compliance().Save(job); result.Err != nil {
return nil, result.Err
} else {
job = result.Data.(*model.Compliance)
go einterfaces.GetComplianceInterface().RunComplianceJob(job)
}
return job, nil
}
func GetComplianceReport(reportId string) (*model.Compliance, *model.AppError) {
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance || einterfaces.GetComplianceInterface() == nil {
return nil, model.NewLocAppError("downloadComplianceReport", "ent.compliance.licence_disable.app_error", nil, "")
}
if result := <-Srv.Store.Compliance().Get(reportId); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.Compliance), nil
}
}
func GetComplianceFile(job *model.Compliance) ([]byte, *model.AppError) {
if f, err := ioutil.ReadFile(*utils.Cfg.ComplianceSettings.Directory + "compliance/" + job.JobName() + ".zip"); err != nil {
return nil, model.NewLocAppError("readFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
} else {
return f, nil
}
}

40
app/ldap.go Обычный файл
Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"net/http"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func SyncLdap() {
go func() {
if utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
ldapI.SyncNow()
} else {
l4g.Error("%v", model.NewLocAppError("ldapSyncNow", "ent.ldap.disabled.app_error", nil, "").Error())
}
}
}()
}
func TestLdap() *model.AppError {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil && utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable {
if err := ldapI.RunTest(); err != nil {
err.StatusCode = 500
return err
}
} else {
err := model.NewLocAppError("ldapTest", "ent.ldap.disabled.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return err
}
return nil
}

Просмотреть файл

@@ -4,15 +4,55 @@
package app package app
import ( import (
"net/http"
"regexp" "regexp"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/dyatlov/go-opengraph/opengraph"
"github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store" "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
func CreatePostAsUser(post *model.Post, teamId string) (*model.Post, *model.AppError) {
// Check that channel has not been deleted
var channel *model.Channel
if result := <-Srv.Store.Channel().Get(post.ChannelId, true); result.Err != nil {
err := model.NewLocAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error())
err.StatusCode = http.StatusBadRequest
return nil, err
} else {
channel = result.Data.(*model.Channel)
}
if channel.DeleteAt != 0 {
err := model.NewLocAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "")
err.StatusCode = http.StatusBadRequest
return nil, err
}
if rp, err := CreatePost(post, teamId, true); err != nil {
if err.Id == "api.post.create_post.root_id.app_error" ||
err.Id == "api.post.create_post.channel_root_id.app_error" ||
err.Id == "api.post.create_post.parent_id.app_error" {
err.StatusCode = http.StatusBadRequest
}
return nil, err
} else {
// Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app)
if _, ok := post.Props["from_webhook"]; !ok {
if result := <-Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, post.UserId); result.Err != nil {
l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, post.UserId, result.Err)
}
}
return rp, nil
}
}
func CreatePost(post *model.Post, teamId string, triggerWebhooks bool) (*model.Post, *model.AppError) { func CreatePost(post *model.Post, teamId string, triggerWebhooks bool) (*model.Post, *model.AppError) {
var pchan store.StoreChannel var pchan store.StoreChannel
if len(post.RootId) > 0 { if len(post.RootId) > 0 {
@@ -194,3 +234,268 @@ func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post {
return post return post
} }
func UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
if utils.IsLicensed {
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER {
err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_denied.app_error", nil, "")
err.StatusCode = http.StatusForbidden
return nil, err
}
}
var oldPost *model.Post
if result := <-Srv.Store.Post().Get(post.Id); result.Err != nil {
return nil, result.Err
} else {
oldPost = result.Data.(*model.PostList).Posts[post.Id]
if oldPost == nil {
err := model.NewLocAppError("updatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id)
err.StatusCode = http.StatusBadRequest
return nil, err
}
if oldPost.UserId != post.UserId {
err := model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil, "oldUserId="+oldPost.UserId)
err.StatusCode = http.StatusBadRequest
return nil, err
}
if oldPost.DeleteAt != 0 {
err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "")
err.StatusCode = http.StatusBadRequest
return nil, err
}
if oldPost.IsSystemMessage() {
err := model.NewLocAppError("updatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id)
err.StatusCode = http.StatusBadRequest
return nil, err
}
if utils.IsLicensed {
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) {
err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}, "")
err.StatusCode = http.StatusBadRequest
return nil, err
}
}
}
newPost := &model.Post{}
*newPost = *oldPost
newPost.Message = post.Message
newPost.EditAt = model.GetMillis()
newPost.Hashtags, _ = model.ParseHashtags(post.Message)
if result := <-Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
return nil, result.Err
} else {
rpost := result.Data.(*model.Post)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
message.Add("post", rpost.ToJson())
go Publish(message)
InvalidateCacheForChannelPosts(rpost.ChannelId)
return rpost, nil
}
}
func GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().GetPosts(channelId, offset, limit, true); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.PostList), nil
}
}
func GetPostsEtag(channelId string) string {
return (<-Srv.Store.Post().GetEtag(channelId, true)).Data.(string)
}
func GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().GetPostsSince(channelId, time, true); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.PostList), nil
}
}
func GetSinglePost(postId string) (*model.Post, *model.AppError) {
if result := <-Srv.Store.Post().GetSingle(postId); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.Post), nil
}
}
func GetPostThread(postId string) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().Get(postId); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.PostList), nil
}
}
func GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().GetFlaggedPosts(userId, offset, limit); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.PostList), nil
}
}
func GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().Get(postId); result.Err != nil {
return nil, result.Err
} else {
list := result.Data.(*model.PostList)
if len(list.Order) != 1 {
return nil, model.NewLocAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "")
}
post := list.Posts[list.Order[0]]
var channel *model.Channel
var err *model.AppError
if channel, err = GetChannel(post.ChannelId); err != nil {
return nil, err
}
if err = JoinChannel(channel, userId); err != nil {
return nil, err
}
return list, nil
}
}
func GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) {
var pchan store.StoreChannel
if before {
pchan = Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset)
} else {
pchan = Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset)
}
if result := <-pchan; result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.PostList), nil
}
}
func DeletePost(postId string) (*model.Post, *model.AppError) {
if result := <-Srv.Store.Post().GetSingle(postId); result.Err != nil {
return nil, result.Err
} else {
post := result.Data.(*model.Post)
if result := <-Srv.Store.Post().Delete(postId, model.GetMillis()); result.Err != nil {
return nil, result.Err
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
message.Add("post", post.ToJson())
go Publish(message)
go DeletePostFiles(post)
go DeleteFlaggedPosts(post.Id)
InvalidateCacheForChannelPosts(post.ChannelId)
return post, nil
}
}
func DeleteFlaggedPosts(postId string) {
if result := <-Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil {
l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err)
return
}
}
func DeletePostFiles(post *model.Post) {
if len(post.FileIds) != 0 {
return
}
if result := <-Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil {
l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err)
}
}
func SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool) (*model.PostList, *model.AppError) {
paramsList := model.ParseSearchParams(terms)
channels := []store.StoreChannel{}
for _, params := range paramsList {
params.OrTerms = isOrSearch
// don't allow users to search for everything
if params.Terms != "*" {
channels = append(channels, Srv.Store.Post().Search(teamId, userId, params))
}
}
posts := &model.PostList{}
for _, channel := range channels {
if result := <-channel; result.Err != nil {
return nil, result.Err
} else {
data := result.Data.(*model.PostList)
posts.Extend(data)
}
}
return posts, nil
}
func GetFileInfosForPost(postId string) ([]*model.FileInfo, *model.AppError) {
pchan := Srv.Store.Post().Get(postId)
fchan := Srv.Store.FileInfo().GetForPost(postId)
var infos []*model.FileInfo
if result := <-fchan; result.Err != nil {
return nil, result.Err
} else {
infos = result.Data.([]*model.FileInfo)
}
if len(infos) == 0 {
// No FileInfos were returned so check if they need to be created for this post
var post *model.Post
if result := <-pchan; result.Err != nil {
return nil, result.Err
} else {
post = result.Data.(*model.PostList).Posts[postId]
}
if len(post.Filenames) > 0 {
// The post has Filenames that need to be replaced with FileInfos
infos = MigrateFilenamesToFileInfos(post)
}
}
return infos, nil
}
func GetOpenGraphMetadata(url string) *opengraph.OpenGraph {
og := opengraph.NewOpenGraph()
res, err := http.Get(url)
defer CloseBody(res)
if err != nil {
return og
}
if err := og.ProcessHTML(res.Body); err != nil {
return og
}
return og
}

67
app/saml.go Обычный файл
Просмотреть файл

@@ -0,0 +1,67 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"io"
"mime/multipart"
"net/http"
"os"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func GetSamlMetadata() (string, *model.AppError) {
samlInterface := einterfaces.GetSamlInterface()
if samlInterface == nil {
err := model.NewLocAppError("GetSamlMetadata", "api.admin.saml.not_available.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return "", err
}
if result, err := samlInterface.GetMetadata(); err != nil {
return "", model.NewLocAppError("GetSamlMetadata", "api.admin.saml.metadata.app_error", nil, "err="+err.Message)
} else {
return result, nil
}
}
func AddSamlCertificate(fileData *multipart.FileHeader) *model.AppError {
file, err := fileData.Open()
defer file.Close()
if err != nil {
return model.NewLocAppError("AddSamlCertificate", "api.admin.add_certificate.open.app_error", nil, err.Error())
}
out, err := os.Create(utils.FindDir("config") + fileData.Filename)
if err != nil {
return model.NewLocAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error())
}
defer out.Close()
io.Copy(out, file)
return nil
}
func RemoveSamlCertificate(filename string) *model.AppError {
if err := os.Remove(utils.FindConfigFile(filename)); err != nil {
return model.NewLocAppError("removeCertificate", "api.admin.remove_certificate.delete.app_error",
map[string]interface{}{"Filename": filename}, err.Error())
}
return nil
}
func GetSamlCertificateStatus() map[string]interface{} {
status := make(map[string]interface{})
status["IdpCertificateFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.IdpCertificateFile)
status["PrivateKeyFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.PrivateKeyFile)
status["PublicCertificateFile"] = utils.FileExistsInConfigFolder(*utils.Cfg.SamlSettings.PublicCertificateFile)
return status
}

Просмотреть файл

@@ -6,7 +6,6 @@ package app
import ( import (
"github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
@@ -124,15 +123,6 @@ func AddSessionToCache(session *model.Session) {
sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*utils.Cfg.ServiceSettings.SessionCacheInMinutes*60)) sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*utils.Cfg.ServiceSettings.SessionCacheInMinutes*60))
} }
func InvalidateAllCaches() {
l4g.Info(utils.T("api.context.invalidate_all_caches"))
sessionCache.Purge()
ClearStatusCache()
store.ClearChannelCaches()
store.ClearUserCaches()
store.ClearPostCaches()
}
func SessionCacheLength() int { func SessionCacheLength() int {
return sessionCache.Len() return sessionCache.Len()
} }

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"html/template"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@@ -17,6 +18,7 @@ import (
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
@@ -66,7 +68,7 @@ func CreateUserWithHash(user *model.User, hash string, data string) (*model.User
return ruser, nil return ruser, nil
} }
func CreateUserWithInviteId(user *model.User, inviteId string) (*model.User, *model.AppError) { func CreateUserWithInviteId(user *model.User, inviteId string, siteURL string) (*model.User, *model.AppError) {
var team *model.Team var team *model.Team
if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
return nil, result.Err return nil, result.Err
@@ -86,6 +88,10 @@ func CreateUserWithInviteId(user *model.User, inviteId string) (*model.User, *mo
AddDirectChannels(team.Id, ruser) AddDirectChannels(team.Id, ruser)
if err := SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, siteURL); err != nil {
l4g.Error(err.Error())
}
return ruser, nil return ruser, nil
} }
@@ -106,6 +112,9 @@ func IsFirstUserAccount() bool {
} }
func CreateUser(user *model.User) (*model.User, *model.AppError) { func CreateUser(user *model.User) (*model.User, *model.AppError) {
if !user.IsSSOUser() && !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) {
return nil, model.NewLocAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "")
}
user.Roles = model.ROLE_SYSTEM_USER.Id user.Roles = model.ROLE_SYSTEM_USER.Id
@@ -217,6 +226,25 @@ func CreateOAuthUser(service string, userData io.Reader, teamId string) (*model.
return ruser, nil return ruser, nil
} }
// Check that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
if len(domains) == 0 {
return true
}
domainArray := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
matched := false
for _, d := range domainArray {
if strings.HasSuffix(strings.ToLower(user.Email), "@"+d) {
matched = true
break
}
}
return matched
}
// Check if the username is already used by another user. Return false if the username is invalid. // Check if the username is already used by another user. Return false if the username is invalid.
func IsUsernameTaken(name string) bool { func IsUsernameTaken(name string) bool {
@@ -547,6 +575,22 @@ func SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppE
return nil return nil
} }
func UpdateActiveNoLdap(userId string, active bool) (*model.User, *model.AppError) {
var user *model.User
var err *model.AppError
if user, err = GetUser(userId); err != nil {
return nil, err
}
if user.IsLDAPUser() {
err := model.NewLocAppError("UpdateActive", "api.user.update_active.no_deactivate_ldap.app_error", nil, "userId="+user.Id)
err.StatusCode = http.StatusBadRequest
return nil, err
}
return UpdateActive(user, active)
}
func UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) { func UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) {
if active { if active {
user.DeleteAt = 0 user.DeleteAt = 0
@@ -616,7 +660,40 @@ func UpdateUser(user *model.User, siteURL string) (*model.User, *model.AppError)
} }
} }
func UpdatePassword(user *model.User, hashedPassword string) *model.AppError { func UpdateUserNotifyProps(userId string, props map[string]string, siteURL string) (*model.User, *model.AppError) {
var user *model.User
var err *model.AppError
if user, err = GetUser(userId); err != nil {
return nil, err
}
user.NotifyProps = props
var ruser *model.User
if ruser, err = UpdateUser(user, siteURL); err != nil {
return nil, err
}
return ruser, nil
}
func UpdatePasswordByUserIdSendEmail(userId, newPassword, method, siteURL string) *model.AppError {
var user *model.User
var err *model.AppError
if user, err = GetUser(userId); err != nil {
return err
}
return UpdatePasswordSendEmail(user, newPassword, method, siteURL)
}
func UpdatePassword(user *model.User, newPassword string) *model.AppError {
if err := utils.IsPasswordValid(newPassword); err != nil {
return err
}
hashedPassword := model.HashPassword(newPassword)
if result := <-Srv.Store.User().UpdatePassword(user.Id, hashedPassword); result.Err != nil { if result := <-Srv.Store.User().UpdatePassword(user.Id, hashedPassword); result.Err != nil {
return model.NewLocAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, result.Err.Error()) return model.NewLocAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, result.Err.Error())
} }
@@ -624,8 +701,8 @@ func UpdatePassword(user *model.User, hashedPassword string) *model.AppError {
return nil return nil
} }
func UpdatePasswordSendEmail(user *model.User, hashedPassword, method, siteURL string) *model.AppError { func UpdatePasswordSendEmail(user *model.User, newPassword, method, siteURL string) *model.AppError {
if err := UpdatePassword(user, hashedPassword); err != nil { if err := UpdatePassword(user, newPassword); err != nil {
return err return err
} }
@@ -638,6 +715,75 @@ func UpdatePasswordSendEmail(user *model.User, hashedPassword, method, siteURL s
return nil return nil
} }
func SendPasswordReset(email string, siteURL string) (bool, *model.AppError) {
var user *model.User
var err *model.AppError
if user, err = GetUserByEmail(email); err != nil {
return false, nil
}
if user.AuthData != nil && len(*user.AuthData) != 0 {
return false, model.NewLocAppError("SendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id)
}
var recovery *model.PasswordRecovery
if recovery, err = CreatePasswordRecovery(user.Id); err != nil {
return false, err
}
T := utils.GetUserTranslations(user.Locale)
link := fmt.Sprintf("%s/reset_password_complete?code=%s", siteURL, url.QueryEscape(recovery.Code))
subject := T("api.templates.reset_subject")
bodyPage := utils.NewHTMLTemplate("reset_body", user.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.reset_body.title")
bodyPage.Html["Info"] = template.HTML(T("api.templates.reset_body.info"))
bodyPage.Props["ResetUrl"] = link
bodyPage.Props["Button"] = T("api.templates.reset_body.button")
if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
return false, model.NewLocAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
}
return true, nil
}
func ResetPasswordFromCode(code, newPassword, siteURL string) *model.AppError {
var recovery *model.PasswordRecovery
var err *model.AppError
if recovery, err = GetPasswordRecovery(code); err != nil {
return err
} else {
if model.GetMillis()-recovery.CreateAt >= model.PASSWORD_RECOVER_EXPIRY_TIME {
return model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "")
}
}
var user *model.User
if user, err = GetUser(recovery.UserId); err != nil {
return err
}
if user.IsSSOUser() {
return model.NewLocAppError("ResetPasswordFromCode", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id)
}
T := utils.GetUserTranslations(user.Locale)
if err := UpdatePasswordSendEmail(user, newPassword, T("api.user.reset_password.method"), siteURL); err != nil {
return err
}
if err := DeletePasswordRecoveryForUser(recovery.UserId); err != nil {
l4g.Error(err.Error())
}
return nil
}
func CreatePasswordRecovery(userId string) (*model.PasswordRecovery, *model.AppError) { func CreatePasswordRecovery(userId string) (*model.PasswordRecovery, *model.AppError) {
recovery := &model.PasswordRecovery{} recovery := &model.PasswordRecovery{}
recovery.UserId = userId recovery.UserId = userId

Просмотреть файл

@@ -25,3 +25,29 @@ func TestIsUsernameTaken(t *testing.T) {
t.FailNow() t.FailNow()
} }
} }
func TestCheckUserDomain(t *testing.T) {
th := Setup().InitBasic()
user := th.BasicUser
cases := []struct {
domains string
matched bool
}{
{"simulator.amazonses.com", true},
{"gmail.com", false},
{"", true},
{"gmail.com simulator.amazonses.com", true},
}
for _, c := range cases {
matched := CheckUserDomain(user, c.domains)
if matched != c.matched {
if c.matched {
t.Logf("'%v' should have matched '%v'", user.Email, c.domains)
} else {
t.Logf("'%v' should not have matched '%v'", user.Email, c.domains)
}
t.FailNow()
}
}
}

Просмотреть файл

@@ -376,6 +376,13 @@ func IsInRole(userRoles string, inRole string) bool {
return false return false
} }
func (u *User) IsSSOUser() bool {
if u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL {
return true
}
return false
}
func (u *User) IsOAuthUser() bool { func (u *User) IsOAuthUser() bool {
if u.AuthService == USER_AUTH_SERVICE_GITLAB { if u.AuthService == USER_AUTH_SERVICE_GITLAB {
return true return true

Просмотреть файл

@@ -222,6 +222,27 @@ func (s SqlPostStore) Get(id string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlPostStore) GetSingle(id string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var post model.Post
err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
if err != nil {
result.Err = model.NewLocAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error())
}
result.Data = &post
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
type etagPosts struct { type etagPosts struct {
Id string Id string
UpdateAt int64 UpdateAt int64

Просмотреть файл

@@ -63,6 +63,29 @@ func TestPostStoreGet(t *testing.T) {
} }
} }
func TestPostStoreGetSingle(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
if r1 := <-store.Post().GetSingle(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Post).CreateAt != o1.CreateAt {
t.Fatal("invalid returned post")
}
}
if err := (<-store.Post().GetSingle("123")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestGetEtagCache(t *testing.T) { func TestGetEtagCache(t *testing.T) {
Setup() Setup()
o1 := &model.Post{} o1 := &model.Post{}

Просмотреть файл

@@ -348,3 +348,25 @@ func (s SqlPreferenceStore) DeleteCategory(userId string, category string) Store
return storeChannel return storeChannel
} }
func (s SqlPreferenceStore) DeleteCategoryAndName(category string, name string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec(
`DELETE FROM
Preferences
WHERE
Name = :Name
AND Category = :Category`, map[string]interface{}{"Name": name, "Category": category}); err != nil {
result.Err = model.NewLocAppError("SqlPreferenceStore.DeleteCategoryAndName", "store.sql_preference.delete.app_error", nil, err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -427,3 +427,48 @@ func TestPreferenceDeleteCategory(t *testing.T) {
t.Fatal("should've returned no preferences") t.Fatal("should've returned no preferences")
} }
} }
func TestPreferenceDeleteCategoryAndName(t *testing.T) {
Setup()
category := model.NewId()
name := model.NewId()
userId := model.NewId()
userId2 := model.NewId()
preference1 := model.Preference{
UserId: userId,
Category: category,
Name: name,
Value: "value1a",
}
preference2 := model.Preference{
UserId: userId2,
Category: category,
Name: name,
Value: "value1a",
}
Must(store.Preference().Save(&model.Preferences{preference1, preference2}))
if prefs := Must(store.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
t.Fatal("should've returned 1 preference")
}
if prefs := Must(store.Preference().GetAll(userId2)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
t.Fatal("should've returned 1 preference")
}
if result := <-store.Preference().DeleteCategoryAndName(category, name); result.Err != nil {
t.Fatal(result.Err)
}
if prefs := Must(store.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
t.Fatal("should've returned no preferences")
}
if prefs := Must(store.Preference().GetAll(userId2)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
t.Fatal("should've returned no preferences")
}
}

Просмотреть файл

@@ -127,6 +127,7 @@ type PostStore interface {
Save(post *model.Post) StoreChannel Save(post *model.Post) StoreChannel
Update(newPost *model.Post, oldPost *model.Post) StoreChannel Update(newPost *model.Post, oldPost *model.Post) StoreChannel
Get(id string) StoreChannel Get(id string) StoreChannel
GetSingle(id string) StoreChannel
Delete(postId string, time int64) StoreChannel Delete(postId string, time int64) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel
GetPosts(channelId string, offset int, limit int, allowFromCache bool) StoreChannel GetPosts(channelId string, offset int, limit int, allowFromCache bool) StoreChannel
@@ -276,6 +277,7 @@ type PreferenceStore interface {
GetAll(userId string) StoreChannel GetAll(userId string) StoreChannel
Delete(userId, category, name string) StoreChannel Delete(userId, category, name string) StoreChannel
DeleteCategory(userId string, category string) StoreChannel DeleteCategory(userId string, category string) StoreChannel
DeleteCategoryAndName(category string, name string) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel
IsFeatureEnabled(feature, userId string) StoreChannel IsFeatureEnabled(feature, userId string) StoreChannel
} }