Merge branch 'master' into PLT-1851

Этот коммит содержится в:
=Corey Hulen
2016-02-04 10:55:44 -08:00
родитель f1bf706242 44c19ee443
Коммит cfd4e4de96
54 изменённых файлов: 3887 добавлений и 1164 удалений

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

@@ -4,12 +4,11 @@
package api package api
import ( import (
"io" "fmt"
"io/ioutil"
"net/http" "net/http"
"path" "net/url"
"strconv"
"strings" "strings"
"time"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -17,630 +16,386 @@ import (
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
type commandHandler func(c *Context, command *model.Command) bool type CommandProvider interface {
GetTrigger() string
GetCommand(c *Context) *model.Command
DoCommand(c *Context, channelId string, message string) *model.CommandResponse
}
var ( var commandProviders = make(map[string]CommandProvider)
cmds = map[string]string{
"logoutCommand": "/logout", func RegisterCommandProvider(newProvider CommandProvider) {
"joinCommand": "/join", commandProviders[newProvider.GetTrigger()] = newProvider
"loadTestCommand": "/loadtest", }
"echoCommand": "/echo",
"shrugCommand": "/shrug", func GetCommandProvider(name string) CommandProvider {
"meCommand": "/me", provider, ok := commandProviders[name]
if ok {
return provider
} }
commands = []commandHandler{
logoutCommand, return nil
joinCommand, }
loadTestCommand,
echoCommand,
shrugCommand,
meCommand,
}
commandNotImplementedErr = model.NewLocAppError("checkCommand", "api.command.no_implemented.app_error", nil, "")
)
var echoSem chan bool
func InitCommand(r *mux.Router) { func InitCommand(r *mux.Router) {
l4g.Debug(utils.T("api.command.init.debug")) l4g.Debug(utils.T("api.command.init.debug"))
r.Handle("/command", ApiUserRequired(command)).Methods("POST")
sr := r.PathPrefix("/commands").Subrouter()
sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST")
sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET")
sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
sr.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST")
sr.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST")
sr.Handle("/test", ApiAppHandler(testCommand)).Methods("POST")
sr.Handle("/test", ApiAppHandler(testCommand)).Methods("GET")
} }
func command(c *Context, w http.ResponseWriter, r *http.Request) { func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
commands := make([]*model.Command, 0, 32)
seen := make(map[string]bool)
for _, value := range commandProviders {
cpy := *value.GetCommand(c)
if cpy.AutoComplete && !seen[cpy.Id] {
cpy.Sanitize()
seen[cpy.Trigger] = true
commands = append(commands, &cpy)
}
}
if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
teamCmds := result.Data.([]*model.Command)
for _, cmd := range teamCmds {
if cmd.AutoComplete && !seen[cmd.Id] {
cmd.Sanitize()
seen[cmd.Trigger] = true
commands = append(commands, cmd)
}
}
}
w.Write([]byte(model.CommandListToJson(commands)))
}
func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
command := strings.TrimSpace(props["command"])
channelId := strings.TrimSpace(props["channelId"])
if len(command) <= 1 || strings.Index(command, "/") != 0 {
c.Err = model.NewLocAppError("executeCommand", "api.command.execute_command.start.app_error", nil, "")
return
}
if len(channelId) > 0 {
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
if !c.HasPermissionsToChannel(cchan, "checkCommand") {
return
}
}
parts := strings.Split(command, " ")
trigger := parts[0][1:]
message := strings.Join(parts[1:], " ")
provider := GetCommandProvider(trigger)
if provider != nil {
response := provider.DoCommand(c, channelId, message)
handleResponse(c, w, response, channelId)
return
} else {
chanChan := Srv.Store.Channel().Get(channelId)
teamChan := Srv.Store.Team().Get(c.Session.TeamId)
userChan := Srv.Store.User().Get(c.Session.UserId)
if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
var team *model.Team
if tr := <-teamChan; tr.Err != nil {
c.Err = tr.Err
return
} else {
team = tr.Data.(*model.Team)
}
var user *model.User
if ur := <-userChan; ur.Err != nil {
c.Err = ur.Err
return
} else {
user = ur.Data.(*model.User)
}
var channel *model.Channel
if cr := <-chanChan; cr.Err != nil {
c.Err = cr.Err
return
} else {
channel = cr.Data.(*model.Channel)
}
teamCmds := result.Data.([]*model.Command)
for _, cmd := range teamCmds {
if trigger == cmd.Trigger {
l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, c.Session.UserId))
p := url.Values{}
p.Set("token", cmd.Token)
p.Set("team_id", cmd.TeamId)
p.Set("team_domain", team.Name)
p.Set("channel_id", channelId)
p.Set("channel_name", channel.Name)
p.Set("user_id", c.Session.UserId)
p.Set("user_name", user.Username)
p.Set("command", "/"+trigger)
p.Set("text", message)
p.Set("response_url", "not supported yet")
method := "POST"
if cmd.Method == model.COMMAND_METHOD_GET {
method = "GET"
}
client := &http.Client{}
req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode()))
req.Header.Set("Accept", "application/json")
if cmd.Method == model.COMMAND_METHOD_POST {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if resp, err := client.Do(req); err != nil {
c.Err = model.NewLocAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error())
} else {
if resp.StatusCode == http.StatusOK {
response := model.CommandResponseFromJson(resp.Body)
if response == nil {
c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "")
} else {
handleResponse(c, w, response, channelId)
}
} else {
body, _ := ioutil.ReadAll(resp.Body)
c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(body))
}
}
return
}
}
}
}
c.Err = model.NewLocAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "")
}
func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandResponse, channelId string) {
if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL {
post := &model.Post{}
post.ChannelId = channelId
post.Message = response.Text
if _, err := CreatePost(c, post, true); err != nil {
c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "")
}
} else if response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL {
// post := &model.Post{}
// post.ChannelId = channelId
// post.Message = "TODO_EPHEMERAL: " + response.Text
// if _, err := CreatePost(c, post, true); err != nil {
// c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "")
// }
}
w.Write([]byte(response.ToJson()))
}
func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
c.Err = model.NewLocAppError("createCommand", "api.command.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("createCommand", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt")
cmd := model.CommandFromJson(r.Body)
if cmd == nil {
c.SetInvalidParam("createCommand", "command")
return
}
cmd.CreatorId = c.Session.UserId
cmd.TeamId = c.Session.TeamId
if result := <-Srv.Store.Command().Save(cmd); result.Err != nil {
c.Err = result.Err
return
} else {
c.LogAudit("success")
rcmd := result.Data.(*model.Command)
w.Write([]byte(rcmd.ToJson()))
}
}
func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
c.Err = model.NewLocAppError("listTeamCommands", "api.command.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("listTeamCommands", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
cmds := result.Data.([]*model.Command)
w.Write([]byte(model.CommandListToJson(cmds)))
}
}
func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
c.Err = model.NewLocAppError("regenCommandToken", "api.command.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("regenCommandToken", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt")
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
command := &model.Command{ id := props["id"]
Command: strings.TrimSpace(props["command"]), if len(id) == 0 {
ChannelId: strings.TrimSpace(props["channelId"]), c.SetInvalidParam("regenCommandToken", "id")
Suggest: props["suggest"] == "true", return
Suggestions: make([]*model.SuggestCommand, 0, 128),
} }
checkCommand(c, command) var cmd *model.Command
if c.Err != nil { if result := <-Srv.Store.Command().Get(id); result.Err != nil {
if c.Err != commandNotImplementedErr { c.Err = result.Err
return return
} else {
c.Err = nil
command.Response = model.RESP_NOT_IMPLEMENTED
w.Write([]byte(command.ToJson()))
return
}
} else { } else {
w.Write([]byte(command.ToJson())) cmd = result.Data.(*model.Command)
if c.Session.TeamId != cmd.TeamId || (c.Session.UserId != cmd.CreatorId && !c.IsTeamAdmin()) {
c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("regenToken", "api.command.regen.app_error", nil, "user_id="+c.Session.UserId)
return
}
}
cmd.Token = model.NewId()
if result := <-Srv.Store.Command().Update(cmd); result.Err != nil {
c.Err = result.Err
return
} else {
w.Write([]byte(result.Data.(*model.Command).ToJson()))
} }
} }
func checkCommand(c *Context, command *model.Command) bool { func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
if len(command.Command) == 0 || strings.Index(command.Command, "/") != 0 { c.Err = model.NewLocAppError("deleteCommand", "api.command.disabled.app_error", nil, "")
c.Err = model.NewLocAppError("checkCommand", "api.command.check_command.start.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented
return false return
} }
if len(command.ChannelId) > 0 { if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("deleteCommand", "api.command.admin_only.app_error", nil, "")
if !c.HasPermissionsToChannel(cchan, "checkCommand") { c.Err.StatusCode = http.StatusForbidden
return true return
} }
} }
if !command.Suggest { c.LogAudit("attempt")
implemented := false
for _, cmd := range cmds { props := model.MapFromJson(r.Body)
bounds := len(cmd)
if len(command.Command) < bounds { id := props["id"]
continue if len(id) == 0 {
} c.SetInvalidParam("deleteCommand", "id")
if command.Command[:bounds] == cmd { return
implemented = true }
}
} if result := <-Srv.Store.Command().Get(id); result.Err != nil {
if !implemented { c.Err = result.Err
c.Err = commandNotImplementedErr return
return false } else {
if c.Session.TeamId != result.Data.(*model.Command).TeamId || (c.Session.UserId != result.Data.(*model.Command).CreatorId && !c.IsTeamAdmin()) {
c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("deleteCommand", "api.command.delete.app_error", nil, "user_id="+c.Session.UserId)
return
} }
} }
for _, v := range commands { if err := (<-Srv.Store.Command().Delete(id, model.GetMillis())).Err; err != nil {
c.Err = err
if v(c, command) || c.Err != nil { return
return true
}
} }
return false c.LogAudit("success")
w.Write([]byte(model.MapToJson(props)))
} }
func logoutCommand(c *Context, command *model.Command) bool { func testCommand(c *Context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
cmd := cmds["logoutCommand"] msg := ""
if r.Method == "POST" {
if strings.Index(command.Command, cmd) == 0 { msg = msg + "\ntoken=" + r.FormValue("token")
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.logout_command.description")}) msg = msg + "\nteam_domain=" + r.FormValue("team_domain")
} else {
if !command.Suggest { body, _ := ioutil.ReadAll(r.Body)
command.GotoLocation = "/logout" msg = string(body)
command.Response = model.RESP_EXECUTED
return true
}
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.logout_command.description")})
} }
return false rc := &model.CommandResponse{
} Text: "test command response " + msg,
ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
func echoCommand(c *Context, command *model.Command) bool { }
cmd := cmds["echoCommand"]
maxThreads := 100 w.Write([]byte(rc.ToJson()))
if !command.Suggest && strings.Index(command.Command, cmd) == 0 {
parameters := strings.SplitN(command.Command, " ", 2)
if len(parameters) != 2 || len(parameters[1]) == 0 {
return false
}
message := strings.Trim(parameters[1], " ")
delay := 0
if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 {
if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil {
delay = checkDelay
}
message = message[1:endMsg]
} else if strings.Index(message, " ") > -1 {
delayIdx := strings.LastIndex(message, " ")
delayStr := strings.Trim(message[delayIdx:], " ")
if checkDelay, err := strconv.Atoi(delayStr); err == nil {
delay = checkDelay
message = message[:delayIdx]
}
}
if delay > 10000 {
c.Err = model.NewLocAppError("echoCommand", "api.command.echo_command.under.app_error", nil, "")
return false
}
if echoSem == nil {
// We want one additional thread allowed so we never reach channel lockup
echoSem = make(chan bool, maxThreads+1)
}
if len(echoSem) >= maxThreads {
c.Err = model.NewLocAppError("echoCommand", "api.command.echo_command.high_volume.app_error", nil, "")
return false
}
echoSem <- true
go func() {
defer func() { <-echoSem }()
post := &model.Post{}
post.ChannelId = command.ChannelId
post.Message = message
time.Sleep(time.Duration(delay) * time.Second)
if _, err := CreatePost(c, post, true); err != nil {
l4g.Error(utils.T("api.command.echo_command.create.error"), err)
}
}()
command.Response = model.RESP_EXECUTED
return true
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.echo_command.description")})
}
return false
}
func meCommand(c *Context, command *model.Command) bool {
cmd := cmds["meCommand"]
if !command.Suggest && strings.Index(command.Command, cmd) == 0 {
message := ""
parameters := strings.SplitN(command.Command, " ", 2)
if len(parameters) > 1 {
message += "*" + parameters[1] + "*"
}
post := &model.Post{}
post.Message = message
post.ChannelId = command.ChannelId
if _, err := CreatePost(c, post, false); err != nil {
l4g.Error(utils.T("api.command.me_command.create.error"), err)
return false
}
command.Response = model.RESP_EXECUTED
return true
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.me_command.description")})
}
return false
}
func shrugCommand(c *Context, command *model.Command) bool {
cmd := cmds["shrugCommand"]
if !command.Suggest && strings.Index(command.Command, cmd) == 0 {
message := `¯\\\_(ツ)_/¯`
parameters := strings.SplitN(command.Command, " ", 2)
if len(parameters) > 1 {
message += " " + parameters[1]
}
post := &model.Post{}
post.Message = message
post.ChannelId = command.ChannelId
if _, err := CreatePost(c, post, false); err != nil {
l4g.Error(utils.T("api.command.shrug_command.create.error"), err)
return false
}
command.Response = model.RESP_EXECUTED
return true
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.shrug_command.description")})
}
return false
}
func joinCommand(c *Context, command *model.Command) bool {
// looks for "/join channel-name"
cmd := cmds["joinCommand"]
if strings.Index(command.Command, cmd) == 0 {
parts := strings.Split(command.Command, " ")
startsWith := ""
if len(parts) == 2 {
startsWith = parts[1]
}
if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
c.Err = result.Err
return false
} else {
channels := result.Data.(*model.ChannelList)
for _, v := range channels.Channels {
if v.Name == startsWith && !command.Suggest {
if v.Type == model.CHANNEL_DIRECT {
return false
}
JoinChannel(c, v.Id, "")
if c.Err != nil {
return false
}
command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name
command.Response = model.RESP_EXECUTED
return true
}
if len(startsWith) == 0 || strings.Index(v.Name, startsWith) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: c.T("api.commmand.join_command.description")})
}
}
}
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.commmand.join_command.description")})
}
return false
}
func loadTestCommand(c *Context, command *model.Command) bool {
cmd := cmds["loadTestCommand"]
// This command is only available when EnableTesting is true
if !utils.Cfg.ServiceSettings.EnableTesting {
return false
}
if strings.Index(command.Command, cmd) == 0 {
if loadTestSetupCommand(c, command) {
return true
}
if loadTestUsersCommand(c, command) {
return true
}
if loadTestChannelsCommand(c, command) {
return true
}
if loadTestPostsCommand(c, command) {
return true
}
if loadTestUrlCommand(c, command) {
return true
}
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.load_test_command.description")})
}
return false
}
func parseRange(command string, cmd string) (utils.Range, bool) {
tokens := strings.Fields(strings.TrimPrefix(command, cmd))
var begin int
var end int
var err1 error
var err2 error
switch {
case len(tokens) == 1:
begin, err1 = strconv.Atoi(tokens[0])
end = begin
if err1 != nil {
return utils.Range{0, 0}, false
}
case len(tokens) >= 2:
begin, err1 = strconv.Atoi(tokens[0])
end, err2 = strconv.Atoi(tokens[1])
if err1 != nil || err2 != nil {
return utils.Range{0, 0}, false
}
default:
return utils.Range{0, 0}, false
}
return utils.Range{begin, end}, true
}
func contains(items []string, token string) bool {
for _, elem := range items {
if elem == token {
return true
}
}
return false
}
func loadTestSetupCommand(c *Context, command *model.Command) bool {
cmd := cmds["loadTestCommand"] + " setup"
if strings.Index(command.Command, cmd) == 0 && !command.Suggest {
tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd))
doTeams := contains(tokens, "teams")
doFuzz := contains(tokens, "fuzz")
numArgs := 0
if doTeams {
numArgs++
}
if doFuzz {
numArgs++
}
var numTeams int
var numChannels int
var numUsers int
var numPosts int
// Defaults
numTeams = 10
numChannels = 10
numUsers = 10
numPosts = 10
if doTeams {
if (len(tokens) - numArgs) >= 4 {
numTeams, _ = strconv.Atoi(tokens[numArgs+0])
numChannels, _ = strconv.Atoi(tokens[numArgs+1])
numUsers, _ = strconv.Atoi(tokens[numArgs+2])
numPosts, _ = strconv.Atoi(tokens[numArgs+3])
}
} else {
if (len(tokens) - numArgs) >= 3 {
numChannels, _ = strconv.Atoi(tokens[numArgs+0])
numUsers, _ = strconv.Atoi(tokens[numArgs+1])
numPosts, _ = strconv.Atoi(tokens[numArgs+2])
}
}
client := model.NewClient(c.GetSiteURL())
if doTeams {
if err := CreateBasicUser(client); err != nil {
l4g.Error(utils.T("api.command.load_test_setup_command.create.error"))
return true
}
client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
environment, err := CreateTestEnvironmentWithTeams(
client,
utils.Range{numTeams, numTeams},
utils.Range{numChannels, numChannels},
utils.Range{numUsers, numUsers},
utils.Range{numPosts, numPosts},
doFuzz)
if err != true {
l4g.Error(utils.T("api.command.load_test_setup_command.create.error"))
return true
} else {
l4g.Info("Testing environment created")
for i := 0; i < len(environment.Teams); i++ {
l4g.Info(utils.T("api.command.load_test_setup_command.created.info"), environment.Teams[i].Name)
l4g.Info(utils.T("api.command.load_test_setup_command.login.info"), environment.Environments[i].Users[0].Email, USER_PASSWORD)
}
}
} else {
client.MockSession(c.Session.Token)
CreateTestEnvironmentInTeam(
client,
c.Session.TeamId,
utils.Range{numChannels, numChannels},
utils.Range{numUsers, numUsers},
utils.Range{numPosts, numPosts},
doFuzz)
}
return true
} else if strings.Index(cmd, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{
Suggestion: cmd,
Description: c.T("api.command.load_test_setup_command.description")})
}
return false
}
func loadTestUsersCommand(c *Context, command *model.Command) bool {
cmd1 := cmds["loadTestCommand"] + " users"
cmd2 := cmds["loadTestCommand"] + " users fuzz"
if strings.Index(command.Command, cmd1) == 0 && !command.Suggest {
cmd := cmd1
doFuzz := false
if strings.Index(command.Command, cmd2) == 0 {
doFuzz = true
cmd = cmd2
}
usersr, err := parseRange(command.Command, cmd)
if err == false {
usersr = utils.Range{10, 15}
}
client := model.NewClient(c.GetSiteURL())
userCreator := NewAutoUserCreator(client, c.Session.TeamId)
userCreator.Fuzzy = doFuzz
userCreator.CreateTestUsers(usersr)
return true
} else if strings.Index(cmd1, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: c.T("api.command.load_test_users_command.users.description")})
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_users_command.fuzz.description")})
} else if strings.Index(cmd2, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_users_command.fuzz.description")})
}
return false
}
func loadTestChannelsCommand(c *Context, command *model.Command) bool {
cmd1 := cmds["loadTestCommand"] + " channels"
cmd2 := cmds["loadTestCommand"] + " channels fuzz"
if strings.Index(command.Command, cmd1) == 0 && !command.Suggest {
cmd := cmd1
doFuzz := false
if strings.Index(command.Command, cmd2) == 0 {
doFuzz = true
cmd = cmd2
}
channelsr, err := parseRange(command.Command, cmd)
if err == false {
channelsr = utils.Range{20, 30}
}
client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Token)
channelCreator := NewAutoChannelCreator(client, c.Session.TeamId)
channelCreator.Fuzzy = doFuzz
channelCreator.CreateTestChannels(channelsr)
return true
} else if strings.Index(cmd1, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: c.T("api.command.load_test_channels_command.channel.description")})
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_channels_command.fuzz.description")})
} else if strings.Index(cmd2, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_channels_command.fuzz.description")})
}
return false
}
func loadTestPostsCommand(c *Context, command *model.Command) bool {
cmd1 := cmds["loadTestCommand"] + " posts"
cmd2 := cmds["loadTestCommand"] + " posts fuzz"
if strings.Index(command.Command, cmd1) == 0 && !command.Suggest {
cmd := cmd1
doFuzz := false
if strings.Index(command.Command, cmd2) == 0 {
cmd = cmd2
doFuzz = true
}
postsr, err := parseRange(command.Command, cmd)
if err == false {
postsr = utils.Range{20, 30}
}
tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd))
rimages := utils.Range{0, 0}
if len(tokens) >= 3 {
if numImages, err := strconv.Atoi(tokens[2]); err == nil {
rimages = utils.Range{numImages, numImages}
}
}
var usernames []string
if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil {
profileUsers := result.Data.(map[string]*model.User)
usernames = make([]string, len(profileUsers))
i := 0
for _, userprof := range profileUsers {
usernames[i] = userprof.Username
i++
}
}
client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Token)
testPoster := NewAutoPostCreator(client, command.ChannelId)
testPoster.Fuzzy = doFuzz
testPoster.Users = usernames
numImages := utils.RandIntFromRange(rimages)
numPosts := utils.RandIntFromRange(postsr)
for i := 0; i < numPosts; i++ {
testPoster.HasImage = (i < numImages)
testPoster.CreateRandomPost()
}
return true
} else if strings.Index(cmd1, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: c.T("api.command.load_test_posts_command.posts.description")})
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_posts_command.fuzz.description")})
} else if strings.Index(cmd2, command.Command) == 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: c.T("api.command.load_test_posts_command.fuzz.description")})
}
return false
}
func loadTestUrlCommand(c *Context, command *model.Command) bool {
cmd := cmds["loadTestCommand"] + " url"
if strings.Index(command.Command, cmd) == 0 && !command.Suggest {
url := ""
parameters := strings.SplitN(command.Command, " ", 3)
if len(parameters) != 3 {
c.Err = model.NewLocAppError("loadTestUrlCommand", "api.command.load_test_url_command.url.app_error", nil, "")
return true
} else {
url = parameters[2]
}
// provide a shortcut to easily access tests stored in doc/developer/tests
if !strings.HasPrefix(url, "http") {
url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url
if path.Ext(url) == "" {
url += ".md"
}
}
var contents io.ReadCloser
if r, err := http.Get(url); err != nil {
c.Err = model.NewLocAppError("loadTestUrlCommand", "api.command.load_test_url_command.file.app_error", nil, err.Error())
return false
} else if r.StatusCode > 400 {
c.Err = model.NewLocAppError("loadTestUrlCommand", "api.command.load_test_url_command.file.app_error", nil, r.Status)
return false
} else {
contents = r.Body
}
bytes := make([]byte, 4000)
// break contents into 4000 byte posts
for {
length, err := contents.Read(bytes)
if err != nil && err != io.EOF {
c.Err = model.NewLocAppError("loadTestUrlCommand", "api.command.load_test_url_command.reading.app_error", nil, err.Error())
return false
}
if length == 0 {
break
}
post := &model.Post{}
post.Message = string(bytes[:length])
post.ChannelId = command.ChannelId
if _, err := CreatePost(c, post, false); err != nil {
l4g.Error(utils.T("api.command.load_test_url_command.create.error"), err)
return false
}
}
command.Response = model.RESP_EXECUTED
return true
} else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 {
command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: c.T("api.command.load_test_url_command.description")})
}
return false
} }

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

@@ -0,0 +1,89 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"strconv"
"strings"
"time"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
var echoSem chan bool
type EchoProvider struct {
}
const (
CMD_ECHO = "echo"
)
func init() {
RegisterCommandProvider(&EchoProvider{})
}
func (me *EchoProvider) GetTrigger() string {
return CMD_ECHO
}
func (me *EchoProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_ECHO,
AutoComplete: true,
AutoCompleteDesc: c.T("api.command_echo.desc"),
AutoCompleteHint: c.T("api.command_echo.hint"),
DisplayName: c.T("api.command_echo.name"),
}
}
func (me *EchoProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
maxThreads := 100
delay := 0
if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 {
if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil {
delay = checkDelay
}
message = message[1:endMsg]
} else if strings.Index(message, " ") > -1 {
delayIdx := strings.LastIndex(message, " ")
delayStr := strings.Trim(message[delayIdx:], " ")
if checkDelay, err := strconv.Atoi(delayStr); err == nil {
delay = checkDelay
message = message[:delayIdx]
}
}
if delay > 10000 {
return &model.CommandResponse{Text: c.T("api.command_echo.delay.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if echoSem == nil {
// We want one additional thread allowed so we never reach channel lockup
echoSem = make(chan bool, maxThreads+1)
}
if len(echoSem) >= maxThreads {
return &model.CommandResponse{Text: c.T("api.command_echo.high_volume.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
echoSem <- true
go func() {
defer func() { <-echoSem }()
post := &model.Post{}
post.ChannelId = channelId
post.Message = message
time.Sleep(time.Duration(delay) * time.Second)
if _, err := CreatePost(c, post, true); err != nil {
l4g.Error(c.T("api.command_echo.create.app_error"), err)
}
}()
return &model.CommandResponse{}
}

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

@@ -0,0 +1,42 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"testing"
"time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
)
func TestEchoCommand(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
echoTestString := "/echo test"
r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Echo command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Echo command failed to send")
}
}

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

@@ -0,0 +1,62 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"github.com/mattermost/platform/model"
)
type JoinProvider struct {
}
const (
CMD_JOIN = "join"
)
func init() {
RegisterCommandProvider(&JoinProvider{})
}
func (me *JoinProvider) GetTrigger() string {
return CMD_JOIN
}
func (me *JoinProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_JOIN,
AutoComplete: true,
AutoCompleteDesc: c.T("api.command_join.desc"),
AutoCompleteHint: c.T("api.command_join.hint"),
DisplayName: c.T("api.command_join.name"),
}
}
func (me *JoinProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
return &model.CommandResponse{Text: c.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
channels := result.Data.(*model.ChannelList)
for _, v := range channels.Channels {
if v.Name == message {
if v.Type == model.CHANNEL_DIRECT {
return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
JoinChannel(c, v.Id, "")
if c.Err != nil {
c.Err = nil
return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + v.Name, Text: c.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
}
return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command_join.missing.app_error")}
}

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

@@ -0,0 +1,71 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"strings"
"testing"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
)
func TestJoinCommands(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel0 = Client.Must(Client.CreateChannel(channel0)).Data.(*model.Channel)
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel1.Id))
channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel2.Id))
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
data := make(map[string]string)
data["user_id"] = user2.Id
channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
rs5 := Client.Must(Client.Command(channel0.Id, "/join "+channel2.Name, false)).Data.(*model.CommandResponse)
if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) {
t.Fatal("failed to join channel")
}
rs6 := Client.Must(Client.Command(channel0.Id, "/join "+channel3.Name, false)).Data.(*model.CommandResponse)
if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) {
t.Fatal("should not have joined direct message channel")
}
c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList)
if len(c1.Channels) != 5 { // 4 because of town-square, off-topic and direct
t.Fatal("didn't join channel")
}
found := false
for _, c := range c1.Channels {
if c.Name == channel2.Name {
found = true
break
}
}
if !found {
t.Fatal("didn't join channel")
}
}

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

@@ -0,0 +1,365 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"io"
"net/http"
"path"
"strconv"
"strings"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
var usage = `Mattermost load testing commands to help configure the system
COMMANDS:
Setup - Creates a testing environment in current team.
/loadtest setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
Example:
/loadtest setup teams fuzz 10 20 50
Users - Add a specified number of random users with fuzz text to current team.
/loadtest users [fuzz] <Min Users> <Max Users>
Example:
/loadtest users fuzz 5 10
Channels - Add a specified number of random channels with fuzz text to current team.
/loadtest channels [fuzz] <Min Channels> <Max Channels>
Example:
/loadtest channels fuzz 5 10
Posts - Add some random posts with fuzz text to current channel.
/loadtest posts [fuzz] <Min Posts> <Max Posts> <Max Images>
Example:
/loadtest posts fuzz 5 10 3
Url - Add a post containing the text from a given url to current channel.
/loadtest url
Example:
/loadtest http://www.example.com/sample_file.md
`
const (
CMD_LOADTEST = "loadtest"
)
type LoadTestProvider struct {
}
func init() {
if !utils.Cfg.ServiceSettings.EnableTesting {
RegisterCommandProvider(&LoadTestProvider{})
}
}
func (me *LoadTestProvider) GetTrigger() string {
return CMD_LOADTEST
}
func (me *LoadTestProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_LOADTEST,
AutoComplete: false,
AutoCompleteDesc: "Debug Load Testing",
AutoCompleteHint: "help",
DisplayName: "loadtest",
}
}
func (me *LoadTestProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
//This command is only available when EnableTesting is true
if !utils.Cfg.ServiceSettings.EnableTesting {
return &model.CommandResponse{}
}
if strings.HasPrefix(message, "setup") {
return me.SetupCommand(c, channelId, message)
}
if strings.HasPrefix(message, "users") {
return me.UsersCommand(c, channelId, message)
}
if strings.HasPrefix(message, "channels") {
return me.ChannelsCommand(c, channelId, message)
}
if strings.HasPrefix(message, "posts") {
return me.PostsCommand(c, channelId, message)
}
if strings.HasPrefix(message, "url") {
return me.UrlCommand(c, channelId, message)
}
return me.HelpCommand(c, channelId, message)
}
func (me *LoadTestProvider) HelpCommand(c *Context, channelId string, message string) *model.CommandResponse {
return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message string) *model.CommandResponse {
tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
doTeams := contains(tokens, "teams")
doFuzz := contains(tokens, "fuzz")
numArgs := 0
if doTeams {
numArgs++
}
if doFuzz {
numArgs++
}
var numTeams int
var numChannels int
var numUsers int
var numPosts int
// Defaults
numTeams = 10
numChannels = 10
numUsers = 10
numPosts = 10
if doTeams {
if (len(tokens) - numArgs) >= 4 {
numTeams, _ = strconv.Atoi(tokens[numArgs+0])
numChannels, _ = strconv.Atoi(tokens[numArgs+1])
numUsers, _ = strconv.Atoi(tokens[numArgs+2])
numPosts, _ = strconv.Atoi(tokens[numArgs+3])
}
} else {
if (len(tokens) - numArgs) >= 3 {
numChannels, _ = strconv.Atoi(tokens[numArgs+0])
numUsers, _ = strconv.Atoi(tokens[numArgs+1])
numPosts, _ = strconv.Atoi(tokens[numArgs+2])
}
}
client := model.NewClient(c.GetSiteURL())
if doTeams {
if err := CreateBasicUser(client); err != nil {
return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
environment, err := CreateTestEnvironmentWithTeams(
client,
utils.Range{numTeams, numTeams},
utils.Range{numChannels, numChannels},
utils.Range{numUsers, numUsers},
utils.Range{numPosts, numPosts},
doFuzz)
if err != true {
return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
l4g.Info("Testing environment created")
for i := 0; i < len(environment.Teams); i++ {
l4g.Info("Team Created: " + environment.Teams[i].Name)
l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD)
}
}
} else {
client.MockSession(c.Session.Token)
CreateTestEnvironmentInTeam(
client,
c.Session.TeamId,
utils.Range{numChannels, numChannels},
utils.Range{numUsers, numUsers},
utils.Range{numPosts, numPosts},
doFuzz)
}
return &model.CommandResponse{Text: "Creating enviroment...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) UsersCommand(c *Context, channelId string, message string) *model.CommandResponse {
cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
doFuzz := false
if strings.Index(cmd, "fuzz") == 0 {
doFuzz = true
cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
}
usersr, err := parseRange(cmd, "")
if err == false {
usersr = utils.Range{2, 5}
}
client := model.NewClient(c.GetSiteURL())
userCreator := NewAutoUserCreator(client, c.Session.TeamId)
userCreator.Fuzzy = doFuzz
userCreator.CreateTestUsers(usersr)
return &model.CommandResponse{Text: "Adding users...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) ChannelsCommand(c *Context, channelId string, message string) *model.CommandResponse {
cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
doFuzz := false
if strings.Index(cmd, "fuzz") == 0 {
doFuzz = true
cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
}
channelsr, err := parseRange(cmd, "")
if err == false {
channelsr = utils.Range{2, 5}
}
client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Token)
channelCreator := NewAutoChannelCreator(client, c.Session.TeamId)
channelCreator.Fuzzy = doFuzz
channelCreator.CreateTestChannels(channelsr)
return &model.CommandResponse{Text: "Adding channels...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message string) *model.CommandResponse {
cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
doFuzz := false
if strings.Index(cmd, "fuzz") == 0 {
doFuzz = true
cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
}
postsr, err := parseRange(cmd, "")
if err == false {
postsr = utils.Range{20, 30}
}
tokens := strings.Fields(cmd)
rimages := utils.Range{0, 0}
if len(tokens) >= 3 {
if numImages, err := strconv.Atoi(tokens[2]); err == nil {
rimages = utils.Range{numImages, numImages}
}
}
var usernames []string
if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil {
profileUsers := result.Data.(map[string]*model.User)
usernames = make([]string, len(profileUsers))
i := 0
for _, userprof := range profileUsers {
usernames[i] = userprof.Username
i++
}
}
client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Token)
testPoster := NewAutoPostCreator(client, channelId)
testPoster.Fuzzy = doFuzz
testPoster.Users = usernames
numImages := utils.RandIntFromRange(rimages)
numPosts := utils.RandIntFromRange(postsr)
for i := 0; i < numPosts; i++ {
testPoster.HasImage = (i < numImages)
testPoster.CreateRandomPost()
}
return &model.CommandResponse{Text: "Adding posts...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) UrlCommand(c *Context, channelId string, message string) *model.CommandResponse {
url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
if len(url) == 0 {
return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
// provide a shortcut to easily access tests stored in doc/developer/tests
if !strings.HasPrefix(url, "http") {
url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url
if path.Ext(url) == "" {
url += ".md"
}
}
var contents io.ReadCloser
if r, err := http.Get(url); err != nil {
return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else if r.StatusCode > 400 {
return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
contents = r.Body
}
bytes := make([]byte, 4000)
// break contents into 4000 byte posts
for {
length, err := contents.Read(bytes)
if err != nil && err != io.EOF {
return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if length == 0 {
break
}
post := &model.Post{}
post.Message = string(bytes[:length])
post.ChannelId = channelId
if _, err := CreatePost(c, post, false); err != nil {
return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
return &model.CommandResponse{Text: "Loading data...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func parseRange(command string, cmd string) (utils.Range, bool) {
tokens := strings.Fields(strings.TrimPrefix(command, cmd))
var begin int
var end int
var err1 error
var err2 error
switch {
case len(tokens) == 1:
begin, err1 = strconv.Atoi(tokens[0])
end = begin
if err1 != nil {
return utils.Range{0, 0}, false
}
case len(tokens) >= 2:
begin, err1 = strconv.Atoi(tokens[0])
end, err2 = strconv.Atoi(tokens[1])
if err1 != nil || err2 != nil {
return utils.Range{0, 0}, false
}
default:
return utils.Range{0, 0}, false
}
return utils.Range{begin, end}, true
}
func contains(items []string, token string) bool {
for _, elem := range items {
if elem == token {
return true
}
}
return false
}

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

@@ -0,0 +1,221 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"strings"
"testing"
"time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
func TestLoadTestHelpCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
rs := Client.Must(Client.Command(channel.Id, "/loadtest help", false)).Data.(*model.CommandResponse)
if !strings.Contains(rs.Text, "Mattermost load testing commands to help") {
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second)
}
func TestLoadTestSetupCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
rs := Client.Must(Client.Command(channel.Id, "/loadtest setup fuzz 1 1 1", false)).Data.(*model.CommandResponse)
if rs.Text != "Creating enviroment..." {
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second)
}
func TestLoadTestUsersCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
rs := Client.Must(Client.Command(channel.Id, "/loadtest users fuzz 1 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding users..." {
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second)
}
func TestLoadTestChannelsCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
rs := Client.Must(Client.Command(channel.Id, "/loadtest channels fuzz 1 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding channels..." {
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second)
}
func TestLoadTestPostsCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
rs := Client.Must(Client.Command(channel.Id, "/loadtest posts fuzz 2 3 2", false)).Data.(*model.CommandResponse)
if rs.Text != "Adding posts..." {
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second)
}
func TestLoadTestUrlCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
command := "/loadtest url "
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" {
t.Fatal("/loadtest url with no url should've failed")
}
command = "/loadtest url http://missingfiletonwhere/path/asdf/qwerty"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Unable to get file" {
t.Log(r.Text)
t.Fatal("/loadtest url with invalid url should've failed")
}
command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading data..." {
t.Fatal("/loadtest url for README.md should've executed")
}
command = "/loadtest url test-emoticons.md"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading data..." {
t.Fatal("/loadtest url for test-emoticons.md should've executed")
}
command = "/loadtest url test-emoticons"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading data..." {
t.Fatal("/loadtest url for test-emoticons should've executed")
}
posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList)
// note that this may make more than 3 posts if files are too long to fit in an individual post
if len(posts.Order) < 3 {
t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?")
}
time.Sleep(2 * time.Second)
}

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

@@ -0,0 +1,37 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"github.com/mattermost/platform/model"
)
type LogoutProvider struct {
}
const (
CMD_LOGOUT = "logout"
)
func init() {
RegisterCommandProvider(&LogoutProvider{})
}
func (me *LogoutProvider) GetTrigger() string {
return CMD_LOGOUT
}
func (me *LogoutProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_LOGOUT,
AutoComplete: true,
AutoCompleteDesc: c.T("api.command_logout.desc"),
AutoCompleteHint: "",
DisplayName: c.T("api.command_logout.name"),
}
}
func (me *LogoutProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
return &model.CommandResponse{GotoLocation: "/logout", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command_logout.success_message")}
}

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

@@ -0,0 +1,32 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"testing"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
)
func TestLogoutTestCommand(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
rs1 := Client.Must(Client.Command(channel1.Id, "/logout", false)).Data.(*model.CommandResponse)
if rs1.GotoLocation != "/logout" {
t.Fatal("failed to logout")
}
}

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

@@ -0,0 +1,37 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"github.com/mattermost/platform/model"
)
type MeProvider struct {
}
const (
CMD_ME = "me"
)
func init() {
RegisterCommandProvider(&MeProvider{})
}
func (me *MeProvider) GetTrigger() string {
return CMD_ME
}
func (me *MeProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_ME,
AutoComplete: true,
AutoCompleteDesc: c.T("api.command_me.desc"),
AutoCompleteHint: c.T("api.command_me.hint"),
DisplayName: c.T("api.command_me.name"),
}
}
func (me *MeProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"}
}

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

@@ -0,0 +1,47 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"testing"
"time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
)
func TestMeCommand(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
testString := "/me hello"
r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Command failed to send")
} else {
if p1.Posts[p1.Order[0]].Message != `*hello*` {
t.Log(p1.Posts[p1.Order[0]].Message)
t.Fatal("invalid shrug reponse")
}
}
}

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

@@ -0,0 +1,42 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"github.com/mattermost/platform/model"
)
type ShrugProvider struct {
}
const (
CMD_SHRUG = "shrug"
)
func init() {
RegisterCommandProvider(&ShrugProvider{})
}
func (me *ShrugProvider) GetTrigger() string {
return CMD_SHRUG
}
func (me *ShrugProvider) GetCommand(c *Context) *model.Command {
return &model.Command{
Trigger: CMD_SHRUG,
AutoComplete: true,
AutoCompleteDesc: c.T("api.command_shrug.desc"),
AutoCompleteHint: c.T("api.command_shrug.hint"),
DisplayName: c.T("api.command_shrug.name"),
}
}
func (me *ShrugProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
rmsg := `¯\\\_(ツ)\_/¯`
if len(message) > 0 {
rmsg = message + " " + rmsg
}
return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg}
}

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

@@ -0,0 +1,47 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"testing"
"time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
)
func TestShrugCommand(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
testString := "/shrug"
r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Command failed to send")
} else {
if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` {
t.Log(p1.Posts[p1.Order[0]].Message)
t.Fatal("invalid shrug reponse")
}
}
}

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

@@ -4,7 +4,6 @@
package api package api
import ( import (
"strings"
"testing" "testing"
"time" "time"
@@ -13,7 +12,7 @@ import (
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
func TestSuggestRootCommands(t *testing.T) { func TestListCommands(t *testing.T) {
Setup() Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
@@ -25,177 +24,89 @@ func TestSuggestRootCommands(t *testing.T) {
Client.LoginByEmail(team.Name, user1.Email, "pwd") Client.LoginByEmail(team.Name, user1.Email, "pwd")
if _, err := Client.Command("", "", true); err == nil { if results, err := Client.ListCommands(); err != nil {
t.Fatal("Should fail") t.Fatal(err)
} } else {
commands := results.Data.([]*model.Command)
foundEcho := false
rs1 := Client.Must(Client.Command("", "/", true)).Data.(*model.Command) for _, command := range commands {
if command.Trigger == "echo" {
foundEcho = true
}
}
hasLogout := false if !foundEcho {
for _, v := range rs1.Suggestions { t.Fatal("Couldn't find echo command")
if v.Suggestion == "/logout" {
hasLogout = true
} }
} }
if !hasLogout {
t.Log(rs1.Suggestions)
t.Fatal("should have logout cmd")
}
rs2 := Client.Must(Client.Command("", "/log", true)).Data.(*model.Command)
if rs2.Suggestions[0].Suggestion != "/logout" {
t.Fatal("should have logout cmd")
}
rs3 := Client.Must(Client.Command("", "/joi", true)).Data.(*model.Command)
if rs3.Suggestions[0].Suggestion != "/join" {
t.Fatal("should have join cmd")
}
rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command)
if rs4.Suggestions[0].Suggestion != "/echo" {
t.Fatal("should have echo cmd")
}
} }
func TestLogoutCommands(t *testing.T) { func TestCreateCommand(t *testing.T) {
Setup() Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command)
if rs1.GotoLocation != "/logout" {
t.Fatal("failed to logout")
}
}
func TestJoinCommands(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel1.Id))
channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel2.Id))
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
data := make(map[string]string)
data["user_id"] = user2.Id
channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
rs1 := Client.Must(Client.Command("", "/join aa", true)).Data.(*model.Command)
if rs1.Suggestions[0].Suggestion != "/join "+channel1.Name {
t.Fatal("should have join cmd")
}
rs2 := Client.Must(Client.Command("", "/join bb", true)).Data.(*model.Command)
if rs2.Suggestions[0].Suggestion != "/join "+channel2.Name {
t.Fatal("should have join cmd")
}
rs3 := Client.Must(Client.Command("", "/join", true)).Data.(*model.Command)
if len(rs3.Suggestions) != 2 {
t.Fatal("should have 2 join cmd")
}
rs4 := Client.Must(Client.Command("", "/join ", true)).Data.(*model.Command)
if len(rs4.Suggestions) != 2 {
t.Fatal("should have 2 join cmd")
}
rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command)
if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) {
t.Fatal("failed to join channel")
}
rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command)
if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) {
t.Fatal("should not have joined direct message channel")
}
c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList)
if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct
t.Fatal("didn't join channel")
}
found := false
for _, c := range c1.Channels {
if c.Name == channel2.Name {
found = true
break
}
}
if !found {
t.Fatal("didn't join channel")
}
}
func TestEchoCommand(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
echoTestString := "/echo test"
r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command)
if r1.Response != model.RESP_EXECUTED {
t.Fatal("Echo command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Echo command failed to send")
}
}
func TestLoadTestUrlCommand(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() { defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}() }()
*utils.Cfg.ServiceSettings.EnableCommands = true
utils.Cfg.ServiceSettings.EnableTesting = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
if _, err := Client.CreateCommand(cmd); err == nil {
t.Fatal("should have failed because not admin")
}
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
var rcmd *model.Command
if result, err := Client.CreateCommand(cmd); err != nil {
t.Fatal(err)
} else {
rcmd = result.Data.(*model.Command)
}
if rcmd.CreatorId != user.Id {
t.Fatal("user ids didn't match")
}
if rcmd.TeamId != team.Id {
t.Fatal("team ids didn't match")
}
cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
if result, err := Client.CreateCommand(cmd); err != nil {
t.Fatal(err)
} else {
if result.Data.(*model.Command).CreatorId != user.Id {
t.Fatal("bad user id wasn't overwritten")
}
if result.Data.(*model.Command).TeamId != team.Id {
t.Fatal("bad team id wasn't overwritten")
}
}
}
func TestListTeamCommands(t *testing.T) {
Setup()
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -204,39 +115,160 @@ func TestLoadTestUrlCommand(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command)
command := "/loadtest url " if result, err := Client.ListTeamCommands(); err != nil {
if _, err := Client.Command(channel.Id, command, false); err == nil { t.Fatal(err)
t.Fatal("/loadtest url with no url should've failed") } else {
} cmds := result.Data.([]*model.Command)
// command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" if len(cmds) != 1 {
// if _, err := Client.Command(channel.Id, command, false); err == nil { t.Fatal("incorrect number of cmd")
// t.Fatal("/loadtest url with invalid url should've failed") }
// } }
}
command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { func TestRegenToken(t *testing.T) {
t.Fatal("/loadtest url for README.md should've executed") Setup()
} enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
command = "/loadtest url test-emoticons.md" utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { }()
t.Fatal("/loadtest url for test-emoticons.md should've executed") *utils.Cfg.ServiceSettings.EnableCommands = true
}
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
command = "/loadtest url test-emoticons" team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED {
t.Fatal("/loadtest url for test-emoticons should've executed") user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList)
// note that this may make more than 3 posts if files are too long to fit in an individual post c := &Context{}
if len(posts.Order) < 3 { c.RequestId = model.NewId()
t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command)
data := make(map[string]string)
data["id"] = cmd.Id
if result, err := Client.RegenCommandToken(data); err != nil {
t.Fatal(err)
} else {
if result.Data.(*model.Command).Token == cmd.Token {
t.Fatal("regen didn't work properly")
}
}
}
func TestDeleteCommand(t *testing.T) {
Setup()
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST}
cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command)
data := make(map[string]string)
data["id"] = cmd.Id
if _, err := Client.DeleteCommand(data); err != nil {
t.Fatal(err)
}
cmds := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command)
if len(cmds) != 0 {
t.Fatal("delete didn't work properly")
}
}
func TestTestCommand(t *testing.T) {
Setup()
enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
defer func() {
utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
}()
*utils.Cfg.ServiceSettings.EnableCommands = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
cmd1 := &model.Command{
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/commands/test",
Method: model.COMMAND_METHOD_POST,
Trigger: "test",
}
cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command)
r1 := Client.Must(Client.Command(channel1.Id, "/test", false)).Data.(*model.CommandResponse)
if r1 == nil {
t.Fatal("Test command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Test command failed to send")
}
cmd2 := &model.Command{
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/commands/test",
Method: model.COMMAND_METHOD_GET,
Trigger: "test2",
}
cmd2 = Client.Must(Client.CreateCommand(cmd2)).Data.(*model.Command)
r2 := Client.Must(Client.Command(channel1.Id, "/test2", false)).Data.(*model.CommandResponse)
if r2 == nil {
t.Fatal("Test2 command failed to execute")
}
time.Sleep(100 * time.Millisecond)
p2 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p2.Order) != 2 {
t.Fatal("Test command failed to send")
} }
} }

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

@@ -1542,6 +1542,10 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError {
return result.Err return result.Err
} }
if result := <-Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil {
return result.Err
}
if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil {
return result.Err return result.Err
} }
@@ -2087,13 +2091,16 @@ func switchToSSO(c *Context, w http.ResponseWriter, r *http.Request) {
func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team, email string) { func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team, email string) {
authData := "" authData := ""
ssoEmail := ""
provider := einterfaces.GetOauthProvider(service) provider := einterfaces.GetOauthProvider(service)
if provider == nil { if provider == nil {
c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error",
map[string]interface{}{"Service": service}, "") map[string]interface{}{"Service": service}, "")
return return
} else { } else {
authData = provider.GetAuthDataFromJson(userData) ssoUser := provider.GetUserFromJson(userData)
authData = ssoUser.AuthData
ssoEmail = ssoUser.Email
} }
if len(authData) == 0 { if len(authData) == 0 {
@@ -2120,7 +2127,7 @@ func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request,
return return
} }
if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData); result.Err != nil { if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData, ssoEmail); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} }

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

@@ -32,6 +32,14 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("createIncomingHook", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt") c.LogAudit("attempt")
hook := model.IncomingWebhookFromJson(r.Body) hook := model.IncomingWebhookFromJson(r.Body)
@@ -79,6 +87,14 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("deleteIncomingHook", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt") c.LogAudit("attempt")
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
@@ -116,7 +132,15 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil { if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("getIncomingHooks", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
if result := <-Srv.Store.Webhook().GetIncomingByTeam(c.Session.TeamId); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
@@ -132,6 +156,14 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("createOutgoingHook", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt") c.LogAudit("attempt")
hook := model.OutgoingWebhookFromJson(r.Body) hook := model.OutgoingWebhookFromJson(r.Body)
@@ -188,7 +220,15 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if result := <-Srv.Store.Webhook().GetOutgoingByCreator(c.Session.UserId); result.Err != nil { if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("getOutgoingHooks", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
if result := <-Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
@@ -204,6 +244,14 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("deleteOutgoingHook", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt") c.LogAudit("attempt")
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
@@ -241,6 +289,14 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request)
return return
} }
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
if !(c.IsSystemAdmin() || c.IsTeamAdmin()) {
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.command.admin_only.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
}
}
c.LogAudit("attempt") c.LogAudit("attempt")
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
@@ -258,7 +314,7 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request)
} else { } else {
hook = result.Data.(*model.OutgoingWebhook) hook = result.Data.(*model.OutgoingWebhook)
if c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() { if c.Session.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() {
c.LogAudit("fail - inappropriate permissions") c.LogAudit("fail - inappropriate permissions")
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.permissions.app_error", nil, "user_id="+c.Session.UserId) c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.permissions.app_error", nil, "user_id="+c.Session.UserId)
return return

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

@@ -13,6 +13,14 @@ import (
func TestCreateIncomingHook(t *testing.T) { func TestCreateIncomingHook(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -21,6 +29,10 @@ func TestCreateIncomingHook(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -76,6 +88,14 @@ func TestCreateIncomingHook(t *testing.T) {
func TestListIncomingHooks(t *testing.T) { func TestListIncomingHooks(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -84,6 +104,10 @@ func TestListIncomingHooks(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -114,6 +138,14 @@ func TestListIncomingHooks(t *testing.T) {
func TestDeleteIncomingHook(t *testing.T) { func TestDeleteIncomingHook(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -122,6 +154,10 @@ func TestDeleteIncomingHook(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -154,6 +190,14 @@ func TestDeleteIncomingHook(t *testing.T) {
func TestCreateOutgoingHook(t *testing.T) { func TestCreateOutgoingHook(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -162,6 +206,10 @@ func TestCreateOutgoingHook(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -217,6 +265,14 @@ func TestCreateOutgoingHook(t *testing.T) {
func TestListOutgoingHooks(t *testing.T) { func TestListOutgoingHooks(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -225,6 +281,10 @@ func TestListOutgoingHooks(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -255,6 +315,14 @@ func TestListOutgoingHooks(t *testing.T) {
func TestDeleteOutgoingHook(t *testing.T) { func TestDeleteOutgoingHook(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -263,6 +331,10 @@ func TestDeleteOutgoingHook(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
@@ -295,6 +367,14 @@ func TestDeleteOutgoingHook(t *testing.T) {
func TestRegenOutgoingHookToken(t *testing.T) { func TestRegenOutgoingHookToken(t *testing.T) {
Setup() Setup()
enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks
enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks
defer func() {
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks
}()
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
@@ -303,6 +383,10 @@ func TestRegenOutgoingHookToken(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}

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

@@ -7,6 +7,8 @@
"EnableOAuthServiceProvider": false, "EnableOAuthServiceProvider": false,
"EnableIncomingWebhooks": false, "EnableIncomingWebhooks": false,
"EnableOutgoingWebhooks": false, "EnableOutgoingWebhooks": false,
"EnableCommands": false,
"EnableOnlyAdminIntegrations": true,
"EnablePostUsernameOverride": false, "EnablePostUsernameOverride": false,
"EnablePostIconOverride": false, "EnablePostIconOverride": false,
"EnableTesting": false, "EnableTesting": false,

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

@@ -7,6 +7,8 @@
"EnableOAuthServiceProvider": false, "EnableOAuthServiceProvider": false,
"EnableIncomingWebhooks": false, "EnableIncomingWebhooks": false,
"EnableOutgoingWebhooks": false, "EnableOutgoingWebhooks": false,
"EnableCommands": false,
"EnableOnlyAdminIntegrations": true,
"EnablePostUsernameOverride": false, "EnablePostUsernameOverride": false,
"EnablePostIconOverride": false, "EnablePostIconOverride": false,
"EnableTesting": false, "EnableTesting": false,

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

@@ -7,6 +7,8 @@
"EnableOAuthServiceProvider": false, "EnableOAuthServiceProvider": false,
"EnableIncomingWebhooks": false, "EnableIncomingWebhooks": false,
"EnableOutgoingWebhooks": false, "EnableOutgoingWebhooks": false,
"EnableCommands": false,
"EnableOnlyAdminIntegrations": true,
"EnablePostUsernameOverride": false, "EnablePostUsernameOverride": false,
"EnablePostIconOverride": false, "EnablePostIconOverride": false,
"EnableTesting": false, "EnableTesting": false,

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

@@ -244,121 +244,141 @@
"translation": "Tried to perform an invalid update of the default channel {{.Channel}}" "translation": "Tried to perform an invalid update of the default channel {{.Channel}}"
}, },
{ {
"id": "api.command.check_command.start.app_error", "id": "api.command.admin_only.app_error",
"translation": "Command must start with /" "translation": "Integrations have been limited to admins only."
}, },
{ {
"id": "api.command.echo_command.create.error", "id": "api.command.delete.app_error",
"translation": "Unable to create /echo post, err=%v" "translation": "Inappropriate permissions to delete command"
}, },
{ {
"id": "api.command.echo_command.description", "id": "api.command.disabled.app_error",
"translation": "Echo back text from your account, /echo \"message\" [delay in seconds]" "translation": "Commands have been disabled by the system admin."
}, },
{ {
"id": "api.command.echo_command.high_volume.app_error", "id": "api.command.execute_command.debug",
"translation": "High volume of echo request, cannot process request" "translation": "Executing cmd=%v userId=%v"
}, },
{ {
"id": "api.command.echo_command.under.app_error", "id": "api.command.execute_command.failed.app_error",
"translation": "Delays must be under 10000 seconds" "translation": "Command with a trigger of '{{.Trigger}}' failed"
},
{
"id": "api.command.execute_command.failed_empty.app_error",
"translation": "Command with a trigger of '{{.Trigger}}' returned an empty response"
},
{
"id": "api.command.execute_command.failed_resp.app_error",
"translation": "Command with a trigger of '{{.Trigger}}' returned response {{.Status}}"
},
{
"id": "api.command.execute_command.not_found.app_error",
"translation": "Command with a trigger of '{{.Trigger}}' not found"
},
{
"id": "api.command.execute_command.save.app_error",
"translation": "An error while saving the command response to the channel"
},
{
"id": "api.command.execute_command.start.app_error",
"translation": "No command trigger found"
}, },
{ {
"id": "api.command.init.debug", "id": "api.command.init.debug",
"translation": "Initializing command api routes" "translation": "Initializing command api routes"
}, },
{ {
"id": "api.command.load_test_channels_command.channel.description", "id": "api.command.regen.app_error",
"translation": "Add a specified number of random channels to current team <MinChannels> <MaxChannels>" "translation": "Inappropriate permissions to regenerate command token"
}, },
{ {
"id": "api.command.load_test_channels_command.fuzz.description", "id": "api.command_echo.create.app_error",
"translation": "Add a specified number of random channels with fuzz text to current team <Min Channels> <Max Channels>" "translation": "Unable to create /echo post, err=%v"
}, },
{ {
"id": "api.command.load_test_command.description", "id": "api.command_echo.delay.app_error",
"translation": "Debug Load Testing" "translation": "Delays must be under 10000 seconds"
}, },
{ {
"id": "api.command.load_test_posts_command.fuzz.description", "id": "api.command_echo.desc",
"translation": "Add some random posts with fuzz text to current channel <Min Posts> <Max Posts> <Min Images> <Max Images>" "translation": "Echo back text from your account"
}, },
{ {
"id": "api.command.load_test_posts_command.posts.description", "id": "api.command_echo.high_volume.app_error",
"translation": "Add some random posts to current channel <Min Posts> <Max Posts> <Min Images> <Max Images>" "translation": "High volume of echo request, cannot process request"
}, },
{ {
"id": "api.command.load_test_setup_command.create.error", "id": "api.command_echo.hint",
"translation": "Failed to create testing environment" "translation": "'message' [delay in seconds]"
}, },
{ {
"id": "api.command.load_test_setup_command.created.info", "id": "api.command_echo.name",
"translation": "Team Created: %v" "translation": "echo"
}, },
{ {
"id": "api.command.load_test_setup_command.description", "id": "api.command_join.desc",
"translation": "Creates a testing environment in current team. [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>"
},
{
"id": "api.command.load_test_setup_command.login.info",
"translation": "\t User to login: %v, %v"
},
{
"id": "api.command.load_test_url_command.create.error",
"translation": "Unable to create post, err=%v"
},
{
"id": "api.command.load_test_url_command.description",
"translation": "Add a post containing the text from a given url to current channel <Url>"
},
{
"id": "api.command.load_test_url_command.file.app_error",
"translation": "Unable to get file"
},
{
"id": "api.command.load_test_url_command.reading.app_error",
"translation": "Encountered error reading file"
},
{
"id": "api.command.load_test_url_command.url.app_error",
"translation": "Command must contain a url"
},
{
"id": "api.command.load_test_users_command.fuzz.description",
"translation": "Add a specified number of random users with fuzz text to current team <Min Users> <Max Users>"
},
{
"id": "api.command.load_test_users_command.users.description",
"translation": "Add a specified number of random users to current team <Min Users> <Max Users>"
},
{
"id": "api.command.logout_command.description",
"translation": "Logout"
},
{
"id": "api.command.me_command.create.error",
"translation": "Unable to create /me post post, err=%v"
},
{
"id": "api.command.me_command.description",
"translation": "Do an action, /me [message]"
},
{
"id": "api.command.no_implemented.app_error",
"translation": "Command not implemented"
},
{
"id": "api.command.shrug_command.create.error",
"translation": "Unable to create /shrug post post, err=%v"
},
{
"id": "api.command.shrug_command.description",
"translation": "Adds ¯\\_(ツ)_/¯ to your message, /shrug [message]"
},
{
"id": "api.commmand.join_command.description",
"translation": "Join the open channel" "translation": "Join the open channel"
}, },
{
"id": "api.command_join.fail.app_error",
"translation": "An error occured while joining the channel."
},
{
"id": "api.command_join.hint",
"translation": "[channel-name]"
},
{
"id": "api.command_join.list.app_error",
"translation": "An error occured while listing channels."
},
{
"id": "api.command_join.missing.app_error",
"translation": "We couldn't find the channel"
},
{
"id": "api.command_join.name",
"translation": "join"
},
{
"id": "api.command_join.success",
"translation": "Joined channel."
},
{
"id": "api.command_logout.desc",
"translation": "Logout of Mattermost"
},
{
"id": "api.command_logout.name",
"translation": "logout"
},
{
"id": "api.command_logout.success_message",
"translation": "Logging out..."
},
{
"id": "api.command_me.desc",
"translation": "Do an action"
},
{
"id": "api.command_me.hint",
"translation": "[message]"
},
{
"id": "api.command_me.name",
"translation": "me"
},
{
"id": "api.command_shrug.desc",
"translation": "Adds ¯\\_(ツ)_/¯ to your message"
},
{
"id": "api.command_shrug.hint",
"translation": "[message]"
},
{
"id": "api.command_shrug.name",
"translation": "shrug"
},
{ {
"id": "api.context.404.app_error", "id": "api.context.404.app_error",
"translation": "Sorry, we could not find the page." "translation": "Sorry, we could not find the page."
@@ -1871,6 +1891,46 @@
"id": "model.client.login.app_error", "id": "model.client.login.app_error",
"translation": "Authentication tokens didn't match" "translation": "Authentication tokens didn't match"
}, },
{
"id": "model.command.is_valid.create_at.app_error",
"translation": "Create at must be a valid time"
},
{
"id": "model.command.is_valid.id.app_error",
"translation": "Invalid Id"
},
{
"id": "model.command.is_valid.method.app_error",
"translation": "Invalid Method"
},
{
"id": "model.command.is_valid.team_id.app_error",
"translation": "Invalid team id"
},
{
"id": "model.command.is_valid.token.app_error",
"translation": "Invalid token"
},
{
"id": "model.command.is_valid.trigger.app_error",
"translation": "Invalid trigger"
},
{
"id": "model.command.is_valid.update_at.app_error",
"translation": "Update at must be a valid time"
},
{
"id": "model.command.is_valid.url.app_error",
"translation": "Invalid url"
},
{
"id": "model.command.is_valid.url_http.app_error",
"translation": "Invalid URL. Must be a valid URL and start with http:// or https://"
},
{
"id": "model.command.is_valid.user_id.app_error",
"translation": "Invalid user id"
},
{ {
"id": "model.config.is_valid.email_reset_salt.app_error", "id": "model.config.is_valid.email_reset_salt.app_error",
"translation": "Invalid password reset salt for email settings. Must be 32 chars or more." "translation": "Invalid password reset salt for email settings. Must be 32 chars or more."
@@ -2555,6 +2615,34 @@
"id": "store.sql_channel.update_member.app_error", "id": "store.sql_channel.update_member.app_error",
"translation": "We encountered an error updating the channel member" "translation": "We encountered an error updating the channel member"
}, },
{
"id": "store.sql_command.save.delete.app_error",
"translation": "We couldn't delete the command"
},
{
"id": "store.sql_command.save.delete_perm.app_error",
"translation": "We couldn't delete the command"
},
{
"id": "store.sql_command.save.get.app_error",
"translation": "We couldn't get the command"
},
{
"id": "store.sql_command.save.get_team.app_error",
"translation": "We couldn't get the commands"
},
{
"id": "store.sql_command.save.saving.app_error",
"translation": "We couldn't save the Command"
},
{
"id": "store.sql_command.save.saving_overwrite.app_error",
"translation": "You cannot overwrite an existing Command"
},
{
"id": "store.sql_command.save.update.app_error",
"translation": "We couldn't update the command"
},
{ {
"id": "store.sql_oauth.get_access_data.app_error", "id": "store.sql_oauth.get_access_data.app_error",
"translation": "We encountered an error finding the access token" "translation": "We encountered an error finding the access token"
@@ -3015,6 +3103,14 @@
"id": "store.sql_user.verify_email.app_error", "id": "store.sql_user.verify_email.app_error",
"translation": "Unable to update verify email field" "translation": "Unable to update verify email field"
}, },
{
"id": "store.sql_webhooks.analytics_incoming_count.app_error",
"translation": "We couldn't count the incoming webhooks"
},
{
"id": "store.sql_webhooks.analytics_outgoing_count.app_error",
"translation": "We couldn't count the outgoing webhooks"
},
{ {
"id": "store.sql_webhooks.delete_incoming.app_error", "id": "store.sql_webhooks.delete_incoming.app_error",
"translation": "We couldn't delete the webhook" "translation": "We couldn't delete the webhook"
@@ -3043,10 +3139,6 @@
"id": "store.sql_webhooks.get_outgoing_by_channel.app_error", "id": "store.sql_webhooks.get_outgoing_by_channel.app_error",
"translation": "We couldn't get the webhooks" "translation": "We couldn't get the webhooks"
}, },
{
"id": "store.sql_webhooks.get_outgoing_by_creator.app_error",
"translation": "We couldn't get the webhooks"
},
{ {
"id": "store.sql_webhooks.get_outgoing_by_team.app_error", "id": "store.sql_webhooks.get_outgoing_by_team.app_error",
"translation": "We couldn't get the webhooks" "translation": "We couldn't get the webhooks"
@@ -3079,14 +3171,6 @@
"id": "store.sql_webhooks.update_outgoing.app_error", "id": "store.sql_webhooks.update_outgoing.app_error",
"translation": "We couldn't update the webhook" "translation": "We couldn't update the webhook"
}, },
{
"id": "store.sql_webhooks.analytics_incoming_count.app_error",
"translation": "We couldn't count the incoming webhooks"
},
{
"id": "store.sql_webhooks.analytics_outgoing_count.app_error",
"translation": "We couldn't count the outgoing webhooks"
},
{ {
"id": "utils.config.load_config.decoding.panic", "id": "utils.config.load_config.decoding.panic",
"translation": "Error decoding config file={{.Filename}}, err={{.Error}}" "translation": "Error decoding config file={{.Filename}}, err={{.Error}}"

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

@@ -244,120 +244,140 @@
"translation": "Intento de realizar una actualización inválida al canal predeterminado {{.Channel}}" "translation": "Intento de realizar una actualización inválida al canal predeterminado {{.Channel}}"
}, },
{ {
"id": "api.command.check_command.start.app_error", "id": "api.command.admin_only.app_error",
"translation": "El comando debe comenzar con /" "translation": "Las ingtegraciones solo pueden ser utilizadas por adminitradores."
}, },
{ {
"id": "api.command.echo_command.create.error", "id": "api.command.delete.app_error",
"translation": "No se puede crear /echo mensaje, err=%v" "translation": "Permisos inapropiados para utilizar el comando de borrar"
}, },
{ {
"id": "api.command.echo_command.description", "id": "api.command.disabled.app_error",
"translation": "Echo del texto desde tu cuenta, /echo \"mensaje\" [retraso en segundos]" "translation": "Los comandos han sido deshabilitados por el administrador de sistema."
}, },
{ {
"id": "api.command.echo_command.high_volume.app_error", "id": "api.command.execute_command.debug",
"translation": "Volumen alto de solicitudes echo, no se puede procesar la solicitud" "translation": "Ejecutando cmd=%v userId=%v"
}, },
{ {
"id": "api.command.echo_command.under.app_error", "id": "api.command.execute_command.failed.app_error",
"translation": "El retraso debe ser menor a 10000 segundos" "translation": "El Comando con el gatillador '{{.Trigger}}' falló"
},
{
"id": "api.command.execute_command.failed_empty.app_error",
"translation": "El Comando con el gatillador '{{.Trigger}}' retorno una respuesta vacía"
},
{
"id": "api.command.execute_command.failed_resp.app_error",
"translation": "El Comando con el gatillador '{{.Trigger}}' retorno la respuesta {{.Status}}"
},
{
"id": "api.command.execute_command.not_found.app_error",
"translation": "No se encontró el Comando con el gatillador '{{.Trigger}}'"
},
{
"id": "api.command.execute_command.save.app_error",
"translation": "Ocurrió un error mientras se guardaba la respuesta del comando en el canal"
},
{
"id": "api.command.execute_command.start.app_error",
"translation": "No se encontró un gatillador para el comando"
}, },
{ {
"id": "api.command.init.debug", "id": "api.command.init.debug",
"translation": "Inicializando rutas del API para los comandos" "translation": "Inicializando rutas del API para los comandos"
}, },
{ {
"id": "api.command.load_test_channels_command.channel.description", "id": "api.command.regen.app_error",
"translation": "Agrega un número específico de canales aleatorios al equipo actual <MinChannels> <MaxChannels>" "translation": "Permisos inapropiados para utilizar le comando de regenerar token"
}, },
{ {
"id": "api.command.load_test_channels_command.fuzz.description", "id": "api.command_echo.create.app_error",
"translation": "Agrega un número específico de canales aleatorios con el texto fuzz al equipo actual <Min Channels> <Max Channels>" "translation": "No se pudo crear el /echo mensaje, err=%v"
}, },
{ {
"id": "api.command.load_test_command.description", "id": "api.command_echo.delay.app_error",
"translation": "Depurar pruebas de carga" "translation": "El retraso debe ser menor a 10000 segundos"
}, },
{ {
"id": "api.command.load_test_posts_command.fuzz.description", "id": "api.command_echo.desc",
"translation": "Agrega algunos mensajes aleatorios con el texto fuzz al canal actual <Min Mensajes> <Max Mensajes> <Min Images> <Max Imágenes>" "translation": "Eco de vuelta un texto utilizando tu cuenta"
}, },
{ {
"id": "api.command.load_test_posts_command.posts.description", "id": "api.command_echo.high_volume.app_error",
"translation": "Agrega algunos mensajes aleatorios al canal actual <Min Mensajes> <Max Mensajes> <Min Images> <Max Imágenes>" "translation": "Demasiadas solicitudes de echo, no pudimos procesar la solicitud"
}, },
{ {
"id": "api.command.load_test_setup_command.create.error", "id": "api.command_echo.hint",
"translation": "Falla al crear el entorno de pruebas" "translation": "'mensaje' [retraso en segundos]"
}, },
{ {
"id": "api.command.load_test_setup_command.created.info", "id": "api.command_echo.name",
"translation": "Equipo Creado: %v" "translation": "echo"
}, },
{ {
"id": "api.command.load_test_setup_command.description", "id": "api.command_join.desc",
"translation": "Crea un entorno de prueba para el equipo actual. [equipos] [fuzz] <Cant Canales> <Cant Usuarios> <Num Mensajes>" "translation": "Unirte a un canal público"
}, },
{ {
"id": "api.command.load_test_setup_command.login.info", "id": "api.command_join.fail.app_error",
"translation": "\t Usuario que inicia sesión: %v, %v" "translation": "Ocurrió un error al unirse al canal."
}, },
{ {
"id": "api.command.load_test_url_command.create.error", "id": "api.command_join.hint",
"translation": "No se pudo crear el mensaje, err=%v" "translation": "[nombre del canal]"
}, },
{ {
"id": "api.command.load_test_url_command.description", "id": "api.command_join.list.app_error",
"translation": "Agraga un mensaje que contiene el texto de un url dado al canal actual <Url>" "translation": "Ocurrió un error al listar los canales."
}, },
{ {
"id": "api.command.load_test_url_command.file.app_error", "id": "api.command_join.missing.app_error",
"translation": "No se puede obtener el archivo" "translation": "No pudimos encontrar el canal"
}, },
{ {
"id": "api.command.load_test_url_command.reading.app_error", "id": "api.command_join.name",
"translation": "Se encontró un error leyendo el archivo" "translation": "unir"
}, },
{ {
"id": "api.command.load_test_url_command.url.app_error", "id": "api.command_join.success",
"translation": "El comando debe contener un url" "translation": "Te uniste al canal."
}, },
{ {
"id": "api.command.load_test_users_command.fuzz.description", "id": "api.command_logout.desc",
"translation": "Agraga un número específico de usuarios aleatorios con el texto fuzz al equipo actual <Min Users> <Max Users>" "translation": "Salir de Mattermost"
}, },
{ {
"id": "api.command.load_test_users_command.users.description", "id": "api.command_logout.name",
"translation": "Agrega un número específico de usuarios aleatorios al equipo actual <Min Users> <Max Users>" "translation": "salir"
}, },
{ {
"id": "api.command.logout_command.description", "id": "api.command_logout.success_message",
"translation": "Cerrar sesión" "translation": "Cerrando sesión..."
}, },
{ {
"id": "api.command.me_command.create.error", "id": "api.command_me.desc",
"translation": "No se pudo crear el mensaje /me mensaje, err=%v" "translation": "Hacer una acción"
}, },
{ {
"id": "api.command.me_command.description", "id": "api.command_me.hint",
"translation": "Realiza una acción, /me [mensaje]" "translation": "[mensaje]"
}, },
{ {
"id": "api.command.no_implemented.app_error", "id": "api.command_me.name",
"translation": "Comando no implementado" "translation": "yo"
}, },
{ {
"id": "api.command.shrug_command.create.error", "id": "api.command_shrug.desc",
"translation": "No se pudo crear el mensaje /shrug mensaje, err=%v" "translation": "Agrega ¯\\_(ツ)_/¯ a tu mensaje"
}, },
{ {
"id": "api.command.shrug_command.description", "id": "api.command_shrug.hint",
"translation": "Agrega ¯\\_(ツ)_/¯ a tu mensaje, /shrug [mensaje]" "translation": "[mensaje]"
}, },
{ {
"id": "api.commmand.join_command.description", "id": "api.command_shrug.name",
"translation": "Unirme al canal abierto" "translation": "npi"
}, },
{ {
"id": "api.context.404.app_error", "id": "api.context.404.app_error",
@@ -1871,6 +1891,46 @@
"id": "model.client.login.app_error", "id": "model.client.login.app_error",
"translation": "Token de autenticación no coincidió" "translation": "Token de autenticación no coincidió"
}, },
{
"id": "model.command.is_valid.create_at.app_error",
"translation": "Fecha de Creación debe ser válida"
},
{
"id": "model.command.is_valid.id.app_error",
"translation": "Id inválido"
},
{
"id": "model.command.is_valid.method.app_error",
"translation": "Método Inválido"
},
{
"id": "model.command.is_valid.team_id.app_error",
"translation": "Id del equipo inválido"
},
{
"id": "model.command.is_valid.token.app_error",
"translation": "Token inválido"
},
{
"id": "model.command.is_valid.trigger.app_error",
"translation": "gatillador inválido"
},
{
"id": "model.command.is_valid.update_at.app_error",
"translation": "Fecha de Actualización debe ser válida"
},
{
"id": "model.command.is_valid.url.app_error",
"translation": "URL inválido"
},
{
"id": "model.command.is_valid.url_http.app_error",
"translation": "URL inválido. Debe ser un URL válido y comenzar con http:// o https://"
},
{
"id": "model.command.is_valid.user_id.app_error",
"translation": "Id de usuario inválido"
},
{ {
"id": "model.config.is_valid.email_reset_salt.app_error", "id": "model.config.is_valid.email_reset_salt.app_error",
"translation": "Salt para restablecer contraseñas en la configuración de correos es inválido. Debe ser de 32 caracteres o más." "translation": "Salt para restablecer contraseñas en la configuración de correos es inválido. Debe ser de 32 caracteres o más."
@@ -2555,6 +2615,34 @@
"id": "store.sql_channel.update_member.app_error", "id": "store.sql_channel.update_member.app_error",
"translation": "Encontramos un error actualizando el miembro del canal" "translation": "Encontramos un error actualizando el miembro del canal"
}, },
{
"id": "store.sql_command.save.delete.app_error",
"translation": "No pudimos eliminar el comando"
},
{
"id": "store.sql_command.save.delete_perm.app_error",
"translation": "No pudimos eliminar el comando"
},
{
"id": "store.sql_command.save.get.app_error",
"translation": "No pudimos obtener el comando"
},
{
"id": "store.sql_command.save.get_team.app_error",
"translation": "No pudimos obtener los comandos"
},
{
"id": "store.sql_command.save.saving.app_error",
"translation": "No pudimos guardar el Comando"
},
{
"id": "store.sql_command.save.saving_overwrite.app_error",
"translation": "No puedes sobreescribir un comando existente"
},
{
"id": "store.sql_command.save.update.app_error",
"translation": "No pudimos actualizar el comando"
},
{ {
"id": "store.sql_oauth.get_access_data.app_error", "id": "store.sql_oauth.get_access_data.app_error",
"translation": "Encontramos un error buscando el token de acceso" "translation": "Encontramos un error buscando el token de acceso"
@@ -3051,10 +3139,6 @@
"id": "store.sql_webhooks.get_outgoing_by_channel.app_error", "id": "store.sql_webhooks.get_outgoing_by_channel.app_error",
"translation": "No pudimos obtener los webhooks" "translation": "No pudimos obtener los webhooks"
}, },
{
"id": "store.sql_webhooks.get_outgoing_by_creator.app_error",
"translation": "No pudimos obtener los webhooks"
},
{ {
"id": "store.sql_webhooks.get_outgoing_by_team.app_error", "id": "store.sql_webhooks.get_outgoing_by_team.app_error",
"translation": "No pudimos obtener los webhooks" "translation": "No pudimos obtener los webhooks"

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

@@ -380,7 +380,34 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
m["command"] = command m["command"] = command
m["channelId"] = channelId m["channelId"] = channelId
m["suggest"] = strconv.FormatBool(suggest) m["suggest"] = strconv.FormatBool(suggest)
if r, err := c.DoApiPost("/command", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/commands/execute", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil
}
}
func (c *Client) ListCommands() (*Result, *AppError) {
if r, err := c.DoApiGet("/commands/list", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil
}
}
func (c *Client) ListTeamCommands() (*Result, *AppError) {
if r, err := c.DoApiGet("/commands/list_team_commands", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil
}
}
func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
if r, err := c.DoApiPost("/commands/create", cmd.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -388,6 +415,24 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
} }
} }
func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) {
if r, err := c.DoApiPost("/commands/regen_token", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
}
}
func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) {
if r, err := c.DoApiPost("/commands/delete", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil {
return nil, err return nil, err

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

@@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@@ -9,28 +9,27 @@ import (
) )
const ( const (
RESP_EXECUTED = "executed" COMMAND_METHOD_POST = "P"
RESP_NOT_IMPLEMENTED = "not implemented" COMMAND_METHOD_GET = "G"
) )
type Command struct { type Command struct {
Command string `json:"command"` Id string `json:"id"`
Response string `json:"response"` Token string `json:"token"`
GotoLocation string `json:"goto_location"` CreateAt int64 `json:"create_at"`
ChannelId string `json:"channel_id"` UpdateAt int64 `json:"update_at"`
Suggest bool `json:"-"` DeleteAt int64 `json:"delete_at"`
Suggestions []*SuggestCommand `json:"suggestions"` CreatorId string `json:"creator_id"`
} TeamId string `json:"team_id"`
Trigger string `json:"trigger"`
func (o *Command) AddSuggestion(suggest *SuggestCommand) { Method string `json:"method"`
Username string `json:"username"`
if o.Suggest { IconURL string `json:"icon_url"`
if o.Suggestions == nil { AutoComplete bool `json:"auto_complete"`
o.Suggestions = make([]*SuggestCommand, 0, 128) AutoCompleteDesc string `json:"auto_complete_desc"`
} AutoCompleteHint string `json:"auto_complete_hint"`
DisplayName string `json:"display_name"`
o.Suggestions = append(o.Suggestions, suggest) URL string `json:"url"`
}
} }
func (o *Command) ToJson() string { func (o *Command) ToJson() string {
@@ -52,3 +51,94 @@ func CommandFromJson(data io.Reader) *Command {
return nil return nil
} }
} }
func CommandListToJson(l []*Command) string {
b, err := json.Marshal(l)
if err != nil {
return ""
} else {
return string(b)
}
}
func CommandListFromJson(data io.Reader) []*Command {
decoder := json.NewDecoder(data)
var o []*Command
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}
func (o *Command) IsValid() *AppError {
if len(o.Id) != 26 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.id.app_error", nil, "")
}
if len(o.Token) != 26 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.token.app_error", nil, "")
}
if o.CreateAt == 0 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.create_at.app_error", nil, "")
}
if o.UpdateAt == 0 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "")
}
if len(o.CreatorId) != 26 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "")
}
if len(o.TeamId) != 26 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "")
}
if len(o.Trigger) > 1024 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "")
}
if len(o.URL) == 0 || len(o.URL) > 1024 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "")
}
if !IsValidHttpUrl(o.URL) {
return NewLocAppError("Command.IsValid", "model.command.is_valid.url_http.app_error", nil, "")
}
if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) {
return NewLocAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "")
}
return nil
}
func (o *Command) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.Token == "" {
o.Token = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *Command) PreUpdate() {
o.UpdateAt = GetMillis()
}
func (o *Command) Sanitize() {
o.Token = ""
o.CreatorId = ""
o.Method = ""
o.URL = ""
o.Username = ""
o.IconURL = ""
}

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

@@ -0,0 +1,41 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
COMMAND_RESPONSE_TYPE_IN_CHANNEL = "in_channel"
COMMAND_RESPONSE_TYPE_EPHEMERAL = "ephemeral"
)
type CommandResponse struct {
ResponseType string `json:"response_type"`
Text string `json:"text"`
GotoLocation string `json:"goto_location"`
Attachments interface{} `json:"attachments"`
}
func (o *CommandResponse) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func CommandResponseFromJson(data io.Reader) *CommandResponse {
decoder := json.NewDecoder(data)
var o CommandResponse
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

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

@@ -0,0 +1,19 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestCommandResponseJson(t *testing.T) {
o := CommandResponse{Text: "test"}
json := o.ToJson()
ro := CommandResponseFromJson(strings.NewReader(json))
if o.Text != ro.Text {
t.Fatal("Ids do not match")
}
}

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

@@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@@ -9,17 +9,89 @@ import (
) )
func TestCommandJson(t *testing.T) { func TestCommandJson(t *testing.T) {
o := Command{Id: NewId()}
json := o.ToJson()
ro := CommandFromJson(strings.NewReader(json))
command := &Command{Command: NewId(), Suggest: true} if o.Id != ro.Id {
command.AddSuggestion(&SuggestCommand{Suggestion: NewId()})
json := command.ToJson()
result := CommandFromJson(strings.NewReader(json))
if command.Command != result.Command {
t.Fatal("Ids do not match")
}
if command.Suggestions[0].Suggestion != result.Suggestions[0].Suggestion {
t.Fatal("Ids do not match") t.Fatal("Ids do not match")
} }
} }
func TestCommandIsValid(t *testing.T) {
o := Command{}
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Id = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.UpdateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreatorId = "123"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreatorId = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Token = "123"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Token = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.TeamId = "123"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.TeamId = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.URL = "nowhere.com/"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.URL = "http://nowhere.com/"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Method = COMMAND_METHOD_GET
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
}
func TestCommandPreSave(t *testing.T) {
o := Command{}
o.PreSave()
}
func TestCommandPreUpdate(t *testing.T) {
o := Command{}
o.PreUpdate()
}

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

@@ -24,24 +24,26 @@ const (
) )
type ServiceSettings struct { type ServiceSettings struct {
ListenAddress string ListenAddress string
MaximumLoginAttempts int MaximumLoginAttempts int
SegmentDeveloperKey string SegmentDeveloperKey string
GoogleDeveloperKey string GoogleDeveloperKey string
EnableOAuthServiceProvider bool EnableOAuthServiceProvider bool
EnableIncomingWebhooks bool EnableIncomingWebhooks bool
EnableOutgoingWebhooks bool EnableOutgoingWebhooks bool
EnablePostUsernameOverride bool EnableCommands *bool
EnablePostIconOverride bool EnableOnlyAdminIntegrations *bool
EnableTesting bool EnablePostUsernameOverride bool
EnableDeveloper *bool EnablePostIconOverride bool
EnableSecurityFixAlert *bool EnableTesting bool
SessionLengthWebInDays *int EnableDeveloper *bool
SessionLengthMobileInDays *int EnableSecurityFixAlert *bool
SessionLengthSSOInDays *int SessionLengthWebInDays *int
SessionCacheInMinutes *int SessionLengthMobileInDays *int
WebsocketSecurePort *int SessionLengthSSOInDays *int
WebsocketPort *int SessionCacheInMinutes *int
WebsocketSecurePort *int
WebsocketPort *int
} }
type SSOSettings struct { type SSOSettings struct {
@@ -349,10 +351,22 @@ func (o *Config) SetDefaults() {
o.ServiceSettings.SessionCacheInMinutes = new(int) o.ServiceSettings.SessionCacheInMinutes = new(int)
*o.ServiceSettings.SessionCacheInMinutes = 10 *o.ServiceSettings.SessionCacheInMinutes = 10
} }
if o.ServiceSettings.EnableCommands == nil {
o.ServiceSettings.EnableCommands = new(bool)
*o.ServiceSettings.EnableCommands = false
}
if o.ServiceSettings.EnableOnlyAdminIntegrations == nil {
o.ServiceSettings.EnableOnlyAdminIntegrations = new(bool)
*o.ServiceSettings.EnableOnlyAdminIntegrations = true
}
if o.ServiceSettings.WebsocketPort == nil { if o.ServiceSettings.WebsocketPort == nil {
o.ServiceSettings.WebsocketPort = new(int) o.ServiceSettings.WebsocketPort = new(int)
*o.ServiceSettings.WebsocketPort = 80 *o.ServiceSettings.WebsocketPort = 80
} }
if o.ServiceSettings.WebsocketSecurePort == nil { if o.ServiceSettings.WebsocketSecurePort == nil {
o.ServiceSettings.WebsocketSecurePort = new(int) o.ServiceSettings.WebsocketSecurePort = new(int)
*o.ServiceSettings.WebsocketSecurePort = 443 *o.ServiceSettings.WebsocketSecurePort = 443

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

@@ -0,0 +1,173 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
)
type SqlCommandStore struct {
*SqlStore
}
func NewSqlCommandStore(sqlStore *SqlStore) CommandStore {
s := &SqlCommandStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
tableo := db.AddTableWithName(model.Command{}, "Commands").SetKeys(false, "Id")
tableo.ColMap("Id").SetMaxSize(26)
tableo.ColMap("Token").SetMaxSize(26)
tableo.ColMap("CreatorId").SetMaxSize(26)
tableo.ColMap("TeamId").SetMaxSize(26)
tableo.ColMap("Trigger").SetMaxSize(128)
tableo.ColMap("URL").SetMaxSize(1024)
tableo.ColMap("Method").SetMaxSize(1)
tableo.ColMap("Username").SetMaxSize(64)
tableo.ColMap("IconURL").SetMaxSize(1024)
tableo.ColMap("AutoCompleteDesc").SetMaxSize(1024)
tableo.ColMap("AutoCompleteHint").SetMaxSize(1024)
tableo.ColMap("DisplayName").SetMaxSize(64)
}
return s
}
func (s SqlCommandStore) UpgradeSchemaIfNeeded() {
}
func (s SqlCommandStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_command_team_id", "Commands", "TeamId")
}
func (s SqlCommandStore) Save(command *model.Command) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(command.Id) > 0 {
result.Err = model.NewLocAppError("SqlCommandStore.Save", "store.sql_command.save.saving_overwrite.app_error", nil, "id="+command.Id)
storeChannel <- result
close(storeChannel)
return
}
command.PreSave()
if result.Err = command.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := s.GetMaster().Insert(command); err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.Save", "store.sql_command.save.saving.app_error", nil, "id="+command.Id+", "+err.Error())
} else {
result.Data = command
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) Get(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var command model.Command
if err := s.GetReplica().SelectOne(&command, "SELECT * FROM Commands WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.Get", "store.sql_command.save.get.app_error", nil, "id="+id+", err="+err.Error())
}
result.Data = &command
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) GetByTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var commands []*model.Command
if _, err := s.GetReplica().Select(&commands, "SELECT * FROM Commands WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.GetByTeam", "store.sql_command.save.get_team.app_error", nil, "teamId="+teamId+", err="+err.Error())
}
result.Data = commands
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) Delete(commandId string, time int64) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("Update Commands SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": commandId})
if err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.Delete", "store.sql_command.save.delete.app_error", nil, "id="+commandId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) PermanentDeleteByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM Commands WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.DeleteByUser", "store.sql_command.save.delete_perm.app_error", nil, "id="+userId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) Update(hook *model.Command) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
hook.UpdateAt = model.GetMillis()
if _, err := s.GetMaster().Update(hook); err != nil {
result.Err = model.NewLocAppError("SqlCommandStore.Update", "store.sql_command.save.update.app_error", nil, "id="+hook.Id+", "+err.Error())
} else {
result.Data = hook
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

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

@@ -0,0 +1,155 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestCommandStoreSave(t *testing.T) {
Setup()
o1 := model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
if err := (<-store.Command().Save(&o1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
if err := (<-store.Command().Save(&o1)).Err; err == nil {
t.Fatal("shouldn't be able to update from save")
}
}
func TestCommandStoreGet(t *testing.T) {
Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
if r1 := <-store.Command().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
t.Fatal("invalid returned command")
}
}
if err := (<-store.Command().Get("123")).Err; err == nil {
t.Fatal("Missing id should have failed")
}
}
func TestCommandStoreGetByTeam(t *testing.T) {
Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
if r1 := <-store.Command().GetByTeam(o1.TeamId); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.([]*model.Command)[0].CreateAt != o1.CreateAt {
t.Fatal("invalid returned command")
}
}
if result := <-store.Command().GetByTeam("123"); result.Err != nil {
t.Fatal(result.Err)
} else {
if len(result.Data.([]*model.Command)) != 0 {
t.Fatal("no commands should have returned")
}
}
}
func TestCommandStoreDelete(t *testing.T) {
Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
if r1 := <-store.Command().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
t.Fatal("invalid returned command")
}
}
if r2 := <-store.Command().Delete(o1.Id, model.GetMillis()); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Command().Get(o1.Id)); r3.Err == nil {
t.Log(r3.Data)
t.Fatal("Missing id should have failed")
}
}
func TestCommandStoreDeleteByUser(t *testing.T) {
Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
if r1 := <-store.Command().Get(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Command).CreateAt != o1.CreateAt {
t.Fatal("invalid returned command")
}
}
if r2 := <-store.Command().PermanentDeleteByUser(o1.CreatorId); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Command().Get(o1.Id)); r3.Err == nil {
t.Log(r3.Data)
t.Fatal("Missing id should have failed")
}
}
func TestCommandStoreUpdate(t *testing.T) {
Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1 = (<-store.Command().Save(o1)).Data.(*model.Command)
o1.Token = model.NewId()
if r2 := <-store.Command().Update(o1); r2.Err != nil {
t.Fatal(r2.Err)
}
}

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

@@ -47,6 +47,7 @@ type SqlStore struct {
oauth OAuthStore oauth OAuthStore
system SystemStore system SystemStore
webhook WebhookStore webhook WebhookStore
command CommandStore
preference PreferenceStore preference PreferenceStore
} }
@@ -100,6 +101,7 @@ func NewSqlStore() Store {
sqlStore.oauth = NewSqlOAuthStore(sqlStore) sqlStore.oauth = NewSqlOAuthStore(sqlStore)
sqlStore.system = NewSqlSystemStore(sqlStore) sqlStore.system = NewSqlSystemStore(sqlStore)
sqlStore.webhook = NewSqlWebhookStore(sqlStore) sqlStore.webhook = NewSqlWebhookStore(sqlStore)
sqlStore.command = NewSqlCommandStore(sqlStore)
sqlStore.preference = NewSqlPreferenceStore(sqlStore) sqlStore.preference = NewSqlPreferenceStore(sqlStore)
err := sqlStore.master.CreateTablesIfNotExists() err := sqlStore.master.CreateTablesIfNotExists()
@@ -116,6 +118,7 @@ func NewSqlStore() Store {
sqlStore.oauth.(*SqlOAuthStore).UpgradeSchemaIfNeeded() sqlStore.oauth.(*SqlOAuthStore).UpgradeSchemaIfNeeded()
sqlStore.system.(*SqlSystemStore).UpgradeSchemaIfNeeded() sqlStore.system.(*SqlSystemStore).UpgradeSchemaIfNeeded()
sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded() sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded()
sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded()
sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded() sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded()
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists() sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
@@ -127,6 +130,7 @@ func NewSqlStore() Store {
sqlStore.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists() sqlStore.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists()
sqlStore.system.(*SqlSystemStore).CreateIndexesIfNotExists() sqlStore.system.(*SqlSystemStore).CreateIndexesIfNotExists()
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists() sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures() sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
@@ -511,6 +515,10 @@ func (ss SqlStore) Webhook() WebhookStore {
return ss.webhook return ss.webhook
} }
func (ss SqlStore) Command() CommandStore {
return ss.command
}
func (ss SqlStore) Preference() PreferenceStore { func (ss SqlStore) Preference() PreferenceStore {
return ss.preference return ss.preference
} }

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

@@ -305,7 +305,7 @@ func (us SqlUserStore) UpdateFailedPasswordAttempts(userId string, attempts int)
return storeChannel return storeChannel
} }
func (us SqlUserStore) UpdateAuthData(userId, service, authData string) StoreChannel { func (us SqlUserStore) UpdateAuthData(userId, service, authData, email string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
@@ -314,7 +314,24 @@ func (us SqlUserStore) UpdateAuthData(userId, service, authData string) StoreCha
updateAt := model.GetMillis() updateAt := model.GetMillis()
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = '', LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0, AuthService = :AuthService, AuthData = :AuthData WHERE Id = :UserId", map[string]interface{}{"LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId, "AuthService": service, "AuthData": authData}); err != nil { query := `
UPDATE
Users
SET
Password = '',
LastPasswordUpdate = :LastPasswordUpdate,
UpdateAt = :UpdateAt,
FailedAttempts = 0,
AuthService = :AuthService,
AuthData = :AuthData`
if len(email) != 0 {
query += ", Email = :Email"
}
query += " WHERE Id = :UserId"
if _, err := us.GetMaster().Exec(query, map[string]interface{}{"LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId, "AuthService": service, "AuthData": authData, "Email": email}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.UpdateAuthData", "store.sql_user.update_auth_data.app_error", nil, "id="+userId+", "+err.Error()) result.Err = model.NewLocAppError("SqlUserStore.UpdateAuthData", "store.sql_user.update_auth_data.app_error", nil, "id="+userId+", "+err.Error())
} else { } else {
result.Data = userId result.Data = userId

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

@@ -402,7 +402,7 @@ func TestUserStoreUpdateAuthData(t *testing.T) {
service := "someservice" service := "someservice"
authData := "1" authData := "1"
if err := (<-store.User().UpdateAuthData(u1.Id, service, authData)).Err; err != nil { if err := (<-store.User().UpdateAuthData(u1.Id, service, authData, "")).Err; err != nil {
t.Fatal(err) t.Fatal(err)
} }

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

@@ -134,7 +134,7 @@ func (s SqlWebhookStore) PermanentDeleteIncomingByUser(userId string) StoreChann
return storeChannel return storeChannel
} }
func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel { func (s SqlWebhookStore) GetIncomingByTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
go func() { go func() {
@@ -142,8 +142,8 @@ func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel {
var webhooks []*model.IncomingWebhook var webhooks []*model.IncomingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE UserId = :UserId AND DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil { if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewLocAppError("SqlWebhookStore.GetIncomingByUser", "store.sql_webhooks.get_incoming_by_user.app_error", nil, "userId="+userId+", err="+err.Error()) result.Err = model.NewLocAppError("SqlWebhookStore.GetIncomingByUser", "store.sql_webhooks.get_incoming_by_user.app_error", nil, "teamId="+teamId+", err="+err.Error())
} }
result.Data = webhooks result.Data = webhooks
@@ -231,27 +231,6 @@ func (s SqlWebhookStore) GetOutgoing(id string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlWebhookStore) GetOutgoingByCreator(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var webhooks []*model.OutgoingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE CreatorId = :UserId AND DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewLocAppError("SqlWebhookStore.GetOutgoingByCreator", "store.sql_webhooks.get_outgoing_by_creator.app_error", nil, "userId="+userId+", err="+err.Error())
}
result.Data = webhooks
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlWebhookStore) GetOutgoingByChannel(channelId string) StoreChannel { func (s SqlWebhookStore) GetOutgoingByChannel(channelId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

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

@@ -48,7 +48,7 @@ func TestWebhookStoreGetIncoming(t *testing.T) {
} }
} }
func TestWebhookStoreGetIncomingByUser(t *testing.T) { func TestWebhookStoreGetIncomingByTeam(t *testing.T) {
Setup() Setup()
o1 := &model.IncomingWebhook{} o1 := &model.IncomingWebhook{}
@@ -58,7 +58,7 @@ func TestWebhookStoreGetIncomingByUser(t *testing.T) {
o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook) o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
if r1 := <-store.Webhook().GetIncomingByUser(o1.UserId); r1.Err != nil { if r1 := <-store.Webhook().GetIncomingByTeam(o1.TeamId); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
if r1.Data.([]*model.IncomingWebhook)[0].CreateAt != o1.CreateAt { if r1.Data.([]*model.IncomingWebhook)[0].CreateAt != o1.CreateAt {
@@ -66,7 +66,7 @@ func TestWebhookStoreGetIncomingByUser(t *testing.T) {
} }
} }
if result := <-store.Webhook().GetIncomingByUser("123"); result.Err != nil { if result := <-store.Webhook().GetIncomingByTeam("123"); result.Err != nil {
t.Fatal(result.Err) t.Fatal(result.Err)
} else { } else {
if len(result.Data.([]*model.IncomingWebhook)) != 0 { if len(result.Data.([]*model.IncomingWebhook)) != 0 {
@@ -201,34 +201,6 @@ func TestWebhookStoreGetOutgoingByChannel(t *testing.T) {
} }
} }
func TestWebhookStoreGetOutgoingByCreator(t *testing.T) {
Setup()
o1 := &model.OutgoingWebhook{}
o1.ChannelId = model.NewId()
o1.CreatorId = model.NewId()
o1.TeamId = model.NewId()
o1.CallbackURLs = []string{"http://nowhere.com/"}
o1 = (<-store.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
if r1 := <-store.Webhook().GetOutgoingByCreator(o1.CreatorId); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt {
t.Fatal("invalid returned webhook")
}
}
if result := <-store.Webhook().GetOutgoingByCreator("123"); result.Err != nil {
t.Fatal(result.Err)
} else {
if len(result.Data.([]*model.OutgoingWebhook)) != 0 {
t.Fatal("no webhooks should have returned")
}
}
}
func TestWebhookStoreGetOutgoingByTeam(t *testing.T) { func TestWebhookStoreGetOutgoingByTeam(t *testing.T) {
Setup() Setup()

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

@@ -37,6 +37,7 @@ type Store interface {
OAuth() OAuthStore OAuth() OAuthStore
System() SystemStore System() SystemStore
Webhook() WebhookStore Webhook() WebhookStore
Command() CommandStore
Preference() PreferenceStore Preference() PreferenceStore
MarkSystemRanUnitTests() MarkSystemRanUnitTests()
Close() Close()
@@ -111,7 +112,7 @@ type UserStore interface {
UpdateLastActivityAt(userId string, time int64) StoreChannel UpdateLastActivityAt(userId string, time int64) StoreChannel
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel
UpdatePassword(userId, newPassword string) StoreChannel UpdatePassword(userId, newPassword string) StoreChannel
UpdateAuthData(userId, service, authData string) StoreChannel UpdateAuthData(userId, service, authData, email string) StoreChannel
Get(id string) StoreChannel Get(id string) StoreChannel
GetProfiles(teamId string) StoreChannel GetProfiles(teamId string) StoreChannel
GetByEmail(teamId string, email string) StoreChannel GetByEmail(teamId string, email string) StoreChannel
@@ -170,13 +171,12 @@ type SystemStore interface {
type WebhookStore interface { type WebhookStore interface {
SaveIncoming(webhook *model.IncomingWebhook) StoreChannel SaveIncoming(webhook *model.IncomingWebhook) StoreChannel
GetIncoming(id string) StoreChannel GetIncoming(id string) StoreChannel
GetIncomingByUser(userId string) StoreChannel GetIncomingByTeam(teamId string) StoreChannel
GetIncomingByChannel(channelId string) StoreChannel GetIncomingByChannel(channelId string) StoreChannel
DeleteIncoming(webhookId string, time int64) StoreChannel DeleteIncoming(webhookId string, time int64) StoreChannel
PermanentDeleteIncomingByUser(userId string) StoreChannel PermanentDeleteIncomingByUser(userId string) StoreChannel
SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel
GetOutgoing(id string) StoreChannel GetOutgoing(id string) StoreChannel
GetOutgoingByCreator(userId string) StoreChannel
GetOutgoingByChannel(channelId string) StoreChannel GetOutgoingByChannel(channelId string) StoreChannel
GetOutgoingByTeam(teamId string) StoreChannel GetOutgoingByTeam(teamId string) StoreChannel
DeleteOutgoing(webhookId string, time int64) StoreChannel DeleteOutgoing(webhookId string, time int64) StoreChannel
@@ -186,6 +186,15 @@ type WebhookStore interface {
AnalyticsOutgoingCount(teamId string) StoreChannel AnalyticsOutgoingCount(teamId string) StoreChannel
} }
type CommandStore interface {
Save(webhook *model.Command) StoreChannel
Get(id string) StoreChannel
GetByTeam(teamId string) StoreChannel
Delete(commandId string, time int64) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel
Update(hook *model.Command) StoreChannel
}
type PreferenceStore interface { type PreferenceStore interface {
Save(preferences *model.Preferences) StoreChannel Save(preferences *model.Preferences) StoreChannel
Get(userId string, category string, name string) StoreChannel Get(userId string, category string, name string) StoreChannel

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

@@ -202,6 +202,8 @@ func getClientConfig(c *model.Config) map[string]string {
props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey
props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks) props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks)
props["EnableOutgoingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableOutgoingWebhooks) props["EnableOutgoingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableOutgoingWebhooks)
props["EnableCommands"] = strconv.FormatBool(*c.ServiceSettings.EnableCommands)
props["EnableOnlyAdminIntegrations"] = strconv.FormatBool(*c.ServiceSettings.EnableOnlyAdminIntegrations)
props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride) props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride)
props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride) props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride)
props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper) props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper)

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

@@ -75,6 +75,8 @@ class ServiceSettings extends React.Component {
config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked;
config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked; config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked;
config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked;
config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked;
//config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; //config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
@@ -386,6 +388,100 @@ class ServiceSettings extends React.Component {
</div> </div>
</div> </div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableCommands'
>
<FormattedMessage
id='admin.service.cmdsTitle'
defaultMessage='Enable Slash Commands: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableCommands'
value='true'
ref='EnableCommands'
defaultChecked={this.props.config.ServiceSettings.EnableCommands}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.service.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableCommands'
value='false'
defaultChecked={!this.props.config.ServiceSettings.EnableCommands}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.service.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.service.cmdsDesc'
defaultMessage='When true, user created slash commands will be allowed.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableOnlyAdminIntegrations'
>
<FormattedMessage
id='admin.service.integrationAdmin'
defaultMessage='Enable Integrations for Admin Only: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableOnlyAdminIntegrations'
value='true'
ref='EnableOnlyAdminIntegrations'
defaultChecked={this.props.config.ServiceSettings.EnableOnlyAdminIntegrations}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.service.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableOnlyAdminIntegrations'
value='false'
defaultChecked={!this.props.config.ServiceSettings.EnableOnlyAdminIntegrations}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.service.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.service.integrationAdminDesc'
defaultMessage='When true, user created integrations can only be created by admins.'
/>
</p>
</div>
</div>
<div className='form-group'> <div className='form-group'>
<label <label
className='control-label col-sm-4' className='control-label col-sm-4'
@@ -393,7 +489,7 @@ class ServiceSettings extends React.Component {
> >
<FormattedMessage <FormattedMessage
id='admin.service.overrideTitle' id='admin.service.overrideTitle'
defaultMessage='Enable Overriding Usernames from Webhooks: ' defaultMessage='Enable Overriding Usernames from Webhooks and Slash Commands: '
/> />
</label> </label>
<div className='col-sm-8'> <div className='col-sm-8'>
@@ -427,7 +523,7 @@ class ServiceSettings extends React.Component {
<p className='help-text'> <p className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.service.overrideDescription' id='admin.service.overrideDescription'
defaultMessage='When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.' defaultMessage='When true, webhooks and slash commands will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'
/> />
</p> </p>
</div> </div>
@@ -440,7 +536,7 @@ class ServiceSettings extends React.Component {
> >
<FormattedMessage <FormattedMessage
id='admin.service.iconTitle' id='admin.service.iconTitle'
defaultMessage='Enable Overriding Icon from Webhooks: ' defaultMessage='Enable Overriding Icon from Webhooks and Slash Commands: '
/> />
</label> </label>
<div className='col-sm-8'> <div className='col-sm-8'>
@@ -474,7 +570,7 @@ class ServiceSettings extends React.Component {
<p className='help-text'> <p className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.service.iconDescription' id='admin.service.iconDescription'
defaultMessage='When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.' defaultMessage='When true, webhooks and slash commands will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'
/> />
</p> </p>
</div> </div>

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

@@ -128,6 +128,16 @@ class ChannelLoader extends React.Component {
} }
}); });
$('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
$(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
} else {
$(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
$(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
}
});
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) { $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
if (ev.type === 'mouseenter') { if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');

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

@@ -202,8 +202,7 @@ class CreateComment extends React.Component {
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') { if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault(); e.preventDefault();
const channelId = ChannelStore.getCurrentId(); const lastPost = PostStore.getCurrentUsersLatestPost(this.props.channelId, this.props.rootId);
const lastPost = PostStore.getCurrentUsersLatestPost(channelId, this.props.rootId);
if (!lastPost) { if (!lastPost) {
return; return;
} }

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

@@ -155,15 +155,10 @@ class CreatePost extends React.Component {
post.message, post.message,
false, false,
(data) => { (data) => {
if (data.response === 'not implemented') {
this.sendMessage(post);
return;
}
PostStore.storeDraft(data.channel_id, null); PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
if (data.goto_location.length > 0) { if (data.goto_location && data.goto_location.length > 0) {
window.location.href = data.goto_location; window.location.href = data.goto_location;
} }
}, },

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

@@ -101,9 +101,9 @@ class FileUpload extends React.Component {
} else if (tooLargeFiles.length > 1) { } else if (tooLargeFiles.length > 1) {
var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', '); var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), files: tooLargeFilenames})); this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filenames: tooLargeFilenames}));
} else if (tooLargeFiles.length > 0) { } else if (tooLargeFiles.length > 0) {
this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), file: tooLargeFiles[0].name})); this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filename: tooLargeFiles[0].name}));
} }
} }

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

@@ -59,64 +59,74 @@ export default class SearchResultsItem extends React.Component {
}; };
return ( return (
<div <div className='search-item__container'>
className='search-item-container post' <div className='date-separator'>
> <hr className='separator__hr' />
<div className='search-channel__name'>{channelName}</div> <div className='separator__text'>
<div className='post__content'> <FormattedDate
<div className='post__img'> value={this.props.post.create_at}
<img day='numeric'
src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()} month='long'
height='36' year='numeric'
width='36'
/> />
</div> </div>
<div> </div>
<ul className='post__header'> <div
<li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li> className='post'
<li className='col'> >
<time className='search-item-time'> <div className='search-channel__name'>{channelName}</div>
<FormattedDate <div className='post__content'>
value={this.props.post.create_at} <div className='post__img'>
day='numeric' <img
month='long' src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
year='numeric' height='36'
hour12={true} width='36'
hour='2-digit'
minute='2-digit'
/>
</time>
</li>
<li>
<a
href='#'
className='search-item__jump'
onClick={this.handleClick}
>
<FormattedMessage
id='search_item.jump'
defaultMessage='Jump'
/>
</a>
</li>
<li>
<a
href='#'
className='comment-icon__container search-item__comment'
onClick={this.handleFocusRHSClick}
>
<span
className='comment-icon'
dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
</a>
</li>
</ul>
<div className='search-item-snippet'>
<span
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
/> />
</div> </div>
<div>
<ul className='post__header'>
<li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
<li className='col'>
<time className='search-item-time'>
<FormattedDate
value={this.props.post.create_at}
hour12={true}
hour='2-digit'
minute='2-digit'
/>
</time>
</li>
<li>
<a
href='#'
className='search-item__jump'
onClick={this.handleClick}
>
<FormattedMessage
id='search_item.jump'
defaultMessage='Jump'
/>
</a>
</li>
<li>
<a
href='#'
className='comment-icon__container search-item__comment'
onClick={this.handleFocusRHSClick}
>
<span
className='comment-icon'
dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
</a>
</li>
</ul>
<div className='search-item-snippet'>
<span
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

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

@@ -0,0 +1,652 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from '../loading_screen.jsx';
import * as Client from '../../utils/client.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
const holders = defineMessages({
requestTypePost: {
id: 'user.settings.cmds.request_type_post',
defaultMessage: 'POST'
},
requestTypeGet: {
id: 'user.settings.cmds.request_type_get',
defaultMessage: 'GET'
},
addDisplayNamePlaceholder: {
id: 'user.settings.cmds.add_display_name.placeholder',
defaultMessage: 'Display Name'
},
addUsernamePlaceholder: {
id: 'user.settings.cmds.add_username.placeholder',
defaultMessage: 'Username'
},
addTriggerPlaceholder: {
id: 'user.settings.cmds.add_trigger.placeholder',
defaultMessage: 'Command trigger e.g. "hello" not including the slash'
},
addAutoCompleteDescPlaceholder: {
id: 'user.settings.cmds.auto_complete_desc.placeholder',
defaultMessage: 'A short description of what this commands does.'
},
addAutoCompleteHintPlaceholder: {
id: 'user.settings.cmds.auto_complete_hint.placeholder',
defaultMessage: '[zipcode]'
},
adUrlPlaceholder: {
id: 'user.settings.cmds.url.placeholder',
defaultMessage: 'Must start with http:// or https://'
}
});
export default class ManageCommandCmds extends React.Component {
constructor() {
super();
this.getCmds = this.getCmds.bind(this);
this.addNewCmd = this.addNewCmd.bind(this);
this.emptyCmd = this.emptyCmd.bind(this);
this.updateTrigger = this.updateTrigger.bind(this);
this.updateURL = this.updateURL.bind(this);
this.updateMethod = this.updateMethod.bind(this);
this.updateUsername = this.updateUsername.bind(this);
this.updateIconURL = this.updateIconURL.bind(this);
this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateAutoComplete = this.updateAutoComplete.bind(this);
this.updateAutoCompleteDesc = this.updateAutoCompleteDesc.bind(this);
this.updateAutoCompleteHint = this.updateAutoCompleteHint.bind(this);
this.state = {cmds: [], cmd: this.emptyCmd(), getCmdsComplete: false};
}
static propTypes() {
return {
intl: intlShape.isRequired
};
}
emptyCmd() {
var cmd = {};
cmd.url = '';
cmd.trigger = '';
cmd.method = 'P';
cmd.username = '';
cmd.icon_url = '';
cmd.auto_complete = false;
cmd.auto_complete_desc = '';
cmd.auto_complete_hint = '';
cmd.display_name = '';
return cmd;
}
componentDidMount() {
this.getCmds();
}
addNewCmd(e) {
e.preventDefault();
if (this.state.cmd.trigger === '' || this.state.cmd.url === '') {
return;
}
var cmd = this.state.cmd;
if (cmd.trigger.length !== 0) {
cmd.trigger = cmd.trigger.trim();
}
cmd.url = cmd.url.trim();
Client.addCommand(
cmd,
(data) => {
let cmds = Object.assign([], this.state.cmds);
if (!cmds) {
cmds = [];
}
cmds.push(data);
this.setState({cmds, addError: null, cmd: this.emptyCmd()});
},
(err) => {
this.setState({addError: err.message});
}
);
}
removeCmd(id) {
const data = {};
data.id = id;
Client.deleteCommand(
data,
() => {
const cmds = this.state.cmds;
let index = -1;
for (let i = 0; i < cmds.length; i++) {
if (cmds[i].id === id) {
index = i;
break;
}
}
if (index !== -1) {
cmds.splice(index, 1);
}
this.setState({cmds});
},
(err) => {
this.setState({editError: err.message});
}
);
}
regenToken(id) {
const regenData = {};
regenData.id = id;
Client.regenCommandToken(
regenData,
(data) => {
const cmds = Object.assign([], this.state.cmds);
for (let i = 0; i < cmds.length; i++) {
if (cmds[i].id === id) {
cmds[i] = data;
break;
}
}
this.setState({cmds, editError: null});
},
(err) => {
this.setState({editError: err.message});
}
);
}
getCmds() {
Client.listTeamCommands(
(data) => {
if (data) {
this.setState({cmds: data, getCmdsComplete: true, editError: null});
}
},
(err) => {
this.setState({editError: err.message});
}
);
}
updateTrigger(e) {
var cmd = this.state.cmd;
cmd.trigger = e.target.value;
this.setState(cmd);
}
updateURL(e) {
var cmd = this.state.cmd;
cmd.url = e.target.value;
this.setState(cmd);
}
updateMethod(e) {
var cmd = this.state.cmd;
cmd.method = e.target.value;
this.setState(cmd);
}
updateUsername(e) {
var cmd = this.state.cmd;
cmd.username = e.target.value;
this.setState(cmd);
}
updateIconURL(e) {
var cmd = this.state.cmd;
cmd.icon_url = e.target.value;
this.setState(cmd);
}
updateDisplayName(e) {
var cmd = this.state.cmd;
cmd.display_name = e.target.value;
this.setState(cmd);
}
updateAutoComplete(e) {
var cmd = this.state.cmd;
cmd.auto_complete = e.target.checked;
this.setState(cmd);
}
updateAutoCompleteDesc(e) {
var cmd = this.state.cmd;
cmd.auto_complete_desc = e.target.value;
this.setState(cmd);
}
updateAutoCompleteHint(e) {
var cmd = this.state.cmd;
cmd.auto_complete_hint = e.target.value;
this.setState(cmd);
}
render() {
let addError;
if (this.state.addError) {
addError = <label className='has-error'>{this.state.addError}</label>;
}
let editError;
if (this.state.editError) {
addError = <label className='has-error'>{this.state.editError}</label>;
}
const cmds = [];
this.state.cmds.forEach((cmd) => {
let triggerDiv;
if (cmd.trigger && cmd.trigger.length !== 0) {
triggerDiv = (
<div className='padding-top'>
<strong>
<FormattedMessage
id='user.settings.cmds.trigger'
defaultMessage='Trigger: '
/>
</strong>{cmd.trigger}
</div>
);
}
cmds.push(
<div
key={cmd.id}
className='webcmd__item'
>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.display_name'
defaultMessage='Display Name: '
/>
</strong><span className='word-break--all'>{cmd.display_name}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.username'
defaultMessage='Username: '
/>
</strong><span className='word-break--all'>{cmd.username}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.icon_url'
defaultMessage='Icon URL: '
/>
</strong><span className='word-break--all'>{cmd.icon_url}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.auto_complete'
defaultMessage='Auto Complete: '
/>
</strong><span className='word-break--all'>{cmd.auto_complete ? 'yes' : 'no'}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.auto_complete_desc'
defaultMessage='Auto Complete Description: '
/>
</strong><span className='word-break--all'>{cmd.auto_complete_desc}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.auto_complete_hint'
defaultMessage='Auto Complete Hint: '
/>
</strong><span className='word-break--all'>{cmd.auto_complete_hint}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.request_type'
defaultMessage='Request Type: '
/>
</strong>
<span className='word-break--all'>
{
cmd.method === 'P' ?
<FormattedMessage
id='user.settings.cmds.request_type_post'
defaultMessage='POST'
/> :
<FormattedMessage
id='user.settings.cmds.request_type_get'
defaultMessage='GET'
/>
}
</span>
</div>
<div className='padding-top x2 webcmd__url'>
<strong>
<FormattedMessage
id='user.settings.cmds.url'
defaultMessage='URL: '
/>
</strong><span className='word-break--all'>{cmd.url}</span>
</div>
{triggerDiv}
<div className='padding-top'>
<strong>
<FormattedMessage
id='user.settings.cmds.token'
defaultMessage='Token: '
/>
</strong>{cmd.token}
</div>
<div className='padding-top'>
<a
className='text-danger'
href='#'
onClick={this.regenToken.bind(this, cmd.id)}
>
<FormattedMessage
id='user.settings.cmds.regen'
defaultMessage='Regen Token'
/>
</a>
<a
className='webcmd__remove'
href='#'
onClick={this.removeCmd.bind(this, cmd.id)}
>
<span aria-hidden='true'>{'×'}</span>
</a>
</div>
<div className='padding-top x2 divider-light'></div>
</div>
);
});
let displayCmds;
if (!this.state.getCmdsComplete) {
displayCmds = <LoadingScreen/>;
} else if (cmds.length > 0) {
displayCmds = cmds;
} else {
displayCmds = (
<div className='padding-top x2'>
<FormattedMessage
id='user.settings.cmds.none'
defaultMessage='None'
/>
</div>
);
}
const existingCmds = (
<div className='webcmds__container'>
<label className='control-label padding-top x2'>
<FormattedMessage
id='user.settings.cmds.existing'
defaultMessage='Existing commands'
/>
</label>
<div className='padding-top divider-light'></div>
<div className='webcmds__list'>
{displayCmds}
</div>
</div>
);
const disableButton = this.state.cmd.trigger === '' || this.state.cmd.url === '';
return (
<div key='addCommandCmd'>
<FormattedHTMLMessage
id='user.settings.cmds.add_desc'
defaultMessage='Create commands to send message events to an external integration. Please see <a href="http://mattermost.org/commands">http://mattermost.org/commands</a> to learn more.'
/>
<div><label className='control-label padding-top x2'>{'Add a new command'}</label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.display_name'
defaultMessage='Display Name: '
/>
</label>
<div className='padding-top'>
<input
ref='displayName'
className='form-control'
value={this.state.cmd.display_name}
onChange={this.updateDisplayName}
placeholder={this.props.intl.formatMessage(holders.addDisplayNamePlaceholder)}
/>
</div>
<div className='padding-top'>{'Command display name.'}</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.username'
defaultMessage='Username: '
/>
</label>
<div className='padding-top'>
<input
ref='username'
className='form-control'
value={this.state.cmd.username}
onChange={this.updateUsername}
placeholder={this.props.intl.formatMessage(holders.addUsernamePlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.username_desc'
defaultMessage='The username to use when overriding the post.'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.icon_url'
defaultMessage='Icon URL: '
/>
</label>
<div className='padding-top'>
<input
ref='iconURL'
className='form-control'
value={this.state.cmd.icon_url}
onChange={this.updateIconURL}
placeholder='https://www.example.com/myicon.png'
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.icon_url_desc'
defaultMessage='URL to an icon'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.trigger'
defaultMessage='Trigger: '
/>
</label>
<div className='padding-top'>
<input
ref='trigger'
className='form-control'
value={this.state.cmd.trigger}
onChange={this.updateTrigger}
placeholder={this.props.intl.formatMessage(holders.addTriggerPlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.trigger_desc'
defaultMessage='Word to trigger on'
/>
{''}</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.auto_complete'
defaultMessage='Auto Complete: '
/>
</label>
<div className='padding-top'>
<label>
<input
type='checkbox'
checked={this.state.cmd.auto_complete}
onChange={this.updateAutoComplete}
/>
<FormattedMessage
id='user.settings.cmds.auto_complete_desc_desc'
defaultMessage='A short description of what this commands does'
/>
</label>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.auto_complete_help'
defaultMessage='Show this command in autocomplete list.'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.auto_complete_desc'
defaultMessage='Auto Complete Description: '
/>
</label>
<div className='padding-top'>
<input
ref='autoCompleteDesc'
className='form-control'
value={this.state.cmd.auto_complete_desc}
onChange={this.updateAutoCompleteDesc}
placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.auto_complete_desc_desc'
defaultMessage='A short description of what this commands does'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.auto_complete_hint'
defaultMessage='Auto Complete Hint: '
/>
</label>
<div className='padding-top'>
<input
ref='autoCompleteHint'
className='form-control'
value={this.state.cmd.auto_complete_hint}
onChange={this.updateAutoCompleteHint}
placeholder={this.props.intl.formatMessage(holders.addAutoCompleteHintPlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.auto_complete_hint_desc'
defaultMessage='List parameters to be passed to the command.'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.request_type'
defaultMessage='Request Type: '
/>
</label>
<div className='padding-top'>
<select
ref='method'
className='form-control'
value={this.state.cmd.method}
onChange={this.updateMethod}
>
<option value='P'>
{this.props.intl.formatMessage(holders.requestTypePost)}
</option>
<option value='G'>
{this.props.intl.formatMessage(holders.requestTypeGet)}
</option>
</select>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.request_type_desc'
defaultMessage='Command request type issued to the callback URL.'
/>
</div>
</div>
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.url'
defaultMessage='URL: '
/>
</label>
<div className='padding-top'>
<input
ref='URL'
className='form-control'
value={this.state.cmd.url}
rows={1}
onChange={this.updateURL}
placeholder={this.props.intl.formatMessage(holders.adUrlPlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.url_desc'
defaultMessage='URL that will receive the HTTP POST or GET event'
/>
</div>
{addError}
</div>
<div className='padding-top padding-bottom'>
<a
className={'btn btn-sm btn-primary'}
href='#'
disabled={disableButton}
onClick={this.addNewCmd}
>
{'Add'}
</a>
</div>
</div>
{existingCmds}
{editError}
</div>
);
}
}
export default injectIntl(ManageCommandCmds);

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

@@ -297,7 +297,7 @@ class UserSettingsDisplay extends React.Component {
if (this.state.nameFormat === 'username') { if (this.state.nameFormat === 'username') {
describe = formatMessage(holders.showUsername); describe = formatMessage(holders.showUsername);
} else if (this.state.nameFormat === 'full_name') { } else if (this.state.nameFormat === 'full_name') {
describe = formatMessage(holders.showFullName); describe = formatMessage(holders.showFullname);
} else { } else {
describe = formatMessage(holders.showNickname); describe = formatMessage(holders.showNickname);
} }

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

@@ -5,6 +5,7 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx'; import SettingItemMax from '../setting_item_max.jsx';
import ManageIncomingHooks from './manage_incoming_hooks.jsx'; import ManageIncomingHooks from './manage_incoming_hooks.jsx';
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx'; import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
import ManageCommandHooks from './manage_command_hooks.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -24,6 +25,14 @@ const holders = defineMessages({
outDesc: { outDesc: {
id: 'user.settings.integrations.outWebhooksDescription', id: 'user.settings.integrations.outWebhooksDescription',
defaultMessage: 'Manage your outgoing webhooks' defaultMessage: 'Manage your outgoing webhooks'
},
cmdName: {
id: 'user.settings.integrations.commands',
defaultMessage: 'Commands'
},
cmdDesc: {
id: 'user.settings.integrations.commandsDescription',
defaultMessage: 'Manage your commands'
} }
}); });
@@ -41,6 +50,7 @@ class UserSettingsIntegrationsTab extends React.Component {
render() { render() {
let incomingHooksSection; let incomingHooksSection;
let outgoingHooksSection; let outgoingHooksSection;
let commandHooksSection;
var inputs = []; var inputs = [];
const {formatMessage} = this.props.intl; const {formatMessage} = this.props.intl;
@@ -106,6 +116,37 @@ class UserSettingsIntegrationsTab extends React.Component {
} }
} }
if (global.window.mm_config.EnableCommands === 'true') {
if (this.props.activeSection === 'command-hooks') {
inputs.push(
<ManageCommandHooks key='command-hook-ui' />
);
commandHooksSection = (
<SettingItemMax
title={formatMessage(holders.cmdName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
this.updateSection('');
e.preventDefault();
}}
/>
);
} else {
commandHooksSection = (
<SettingItemMin
title={formatMessage(holders.cmdName)}
width='medium'
describe={formatMessage(holders.cmdDesc)}
updateSection={() => {
this.updateSection('command-hooks');
}}
/>
);
}
}
return ( return (
<div> <div>
<div className='modal-header'> <div className='modal-header'>
@@ -144,6 +185,8 @@ class UserSettingsIntegrationsTab extends React.Component {
<div className='divider-light'/> <div className='divider-light'/>
{outgoingHooksSection} {outgoingHooksSection}
<div className='divider-dark'/> <div className='divider-dark'/>
{commandHooksSection}
<div className='divider-dark'/>
</div> </div>
</div> </div>
); );

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

@@ -774,20 +774,27 @@ export function savePreferences(preferences, success, error) {
} }
export function getSuggestedCommands(command, suggestionId, component) { export function getSuggestedCommands(command, suggestionId, component) {
client.executeCommand( client.listCommands(
'',
command,
true,
(data) => { (data) => {
var matches = [];
data.forEach((cmd) => {
if (('/' + cmd.trigger).indexOf(command) === 0) {
matches.push({
suggestion: '/' + cmd.trigger + ' ' + cmd.auto_complete_hint,
description: cmd.auto_complete_desc
});
}
});
// pull out the suggested commands from the returned data // pull out the suggested commands from the returned data
const terms = data.suggestions.map((suggestion) => suggestion.suggestion); const terms = matches.map((suggestion) => suggestion.suggestion);
AppDispatcher.handleServerAction({ AppDispatcher.handleServerAction({
type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
id: suggestionId, id: suggestionId,
matchedPretext: command, matchedPretext: command,
terms, terms,
items: data.suggestions, items: matches,
component component
}); });
}, },

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

@@ -908,11 +908,11 @@ export function getChannelExtraInfo(id, memberLimit, success, error) {
export function executeCommand(channelId, command, suggest, success, error) { export function executeCommand(channelId, command, suggest, success, error) {
$.ajax({ $.ajax({
url: '/api/v1/command', url: '/api/v1/commands/execute',
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
type: 'POST', type: 'POST',
data: JSON.stringify({channelId: channelId, command: command, suggest: '' + suggest}), data: JSON.stringify({channelId, command, suggest: '' + suggest}),
success, success,
error: function onError(xhr, status, err) { error: function onError(xhr, status, err) {
var e = handleError('executeCommand', xhr, status, err); var e = handleError('executeCommand', xhr, status, err);
@@ -921,6 +921,78 @@ export function executeCommand(channelId, command, suggest, success, error) {
}); });
} }
export function addCommand(cmd, success, error) {
$.ajax({
url: '/api/v1/commands/create',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(cmd),
success,
error: (xhr, status, err) => {
var e = handleError('addCommand', xhr, status, err);
error(e);
}
});
}
export function deleteCommand(data, success, error) {
$.ajax({
url: '/api/v1/commands/delete',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success,
error: (xhr, status, err) => {
var e = handleError('deleteCommand', xhr, status, err);
error(e);
}
});
}
export function listTeamCommands(success, error) {
$.ajax({
url: '/api/v1/commands/list_team_commands',
dataType: 'json',
type: 'GET',
success,
error: (xhr, status, err) => {
var e = handleError('listTeamCommands', xhr, status, err);
error(e);
}
});
}
export function regenCommandToken(data, success, error) {
$.ajax({
url: '/api/v1/commands/regen_token',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success,
error: (xhr, status, err) => {
var e = handleError('regenCommandToken', xhr, status, err);
error(e);
}
});
}
export function listCommands(success, error) {
$.ajax({
url: '/api/v1/commands/list',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success,
error: function onError(xhr, status, err) {
var e = handleError('listCommands', xhr, status, err);
error(e);
}
});
}
export function getPostsPage(channelId, offset, limit, success, error, complete) { export function getPostsPage(channelId, offset, limit, success, error, complete) {
$.ajax({ $.ajax({
cache: false, cache: false,

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

@@ -173,6 +173,7 @@
.team__name { .team__name {
line-height: 22px; line-height: 22px;
margin-top: -2px; margin-top: -2px;
float: left;
} }
.user__name { .user__name {
@include single-transition(all, 0.1s, linear); @include single-transition(all, 0.1s, linear);

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

@@ -764,12 +764,13 @@ body.ios {
} }
svg { svg {
height: 17px;
width: 17px; width: 17px;
} }
.comment-icon { .comment-icon {
display: inline-block; display: inline-block;
top: 3px; top: 2px;
position: relative; position: relative;
margin-right: 3px; margin-right: 3px;
fill: inherit; fill: inherit;

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

@@ -90,6 +90,7 @@
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
@include flex(1 1 auto); @include flex(1 1 auto);
height: calc(100% - 56px); height: calc(100% - 56px);
padding-top: 10px;
} }
.search-results-header { .search-results-header {
@@ -104,19 +105,22 @@
border-bottom: $border-gray; border-bottom: $border-gray;
} }
.search-item-container { .search-item__container {
border-top: $border-gray;
padding: 10px 1em;
margin: 0;
&:first-child { .post {
border: none; padding: 0 1em 1em;
margin: 0;
&:first-child {
border: none;
}
.search-channel__name {
font-weight: 600;
margin: 0 0 10px 0;
}
} }
.search-channel__name {
font-weight: 600;
margin: 0 0 10px 0;
}
} }
.search-item__jump { .search-item__jump {

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

@@ -351,10 +351,14 @@
"admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.", "admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.",
"admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ", "admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ",
"admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.", "admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.",
"admin.service.overrideTitle": "Enable Overriding Usernames from Webhooks: ", "admin.service.cmdsTitle": "Enable Slash Commands: ",
"admin.service.overrideDescription": "When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.", "admin.service.cmdsDesc": "When true, user created slash commands will be allowed.",
"admin.service.iconTitle": "Enable Overriding Icon from Webhooks: ", "admin.service.integrationAdmin": "Enable Integrations for Admin Only: ",
"admin.service.iconDescription": "When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.", "admin.service.integrationAdminDesc": "When true, user created integrations can only be created by admins.",
"admin.service.overrideTitle": "Enable Overriding Usernames from Webhooks and Salsh Commands: ",
"admin.service.overrideDescription": "When true, webhooks and slash commands will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.",
"admin.service.iconTitle": "Enable Overriding Icon from Webhooks and Slash Commands: ",
"admin.service.iconDescription": "When true, webhooks and slash commands will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.",
"admin.service.testingTitle": "Enable Testing: ", "admin.service.testingTitle": "Enable Testing: ",
"admin.service.testingDescription": "(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.", "admin.service.testingDescription": "(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.",
"admin.service.developerTitle": "Enable Developer Mode: ", "admin.service.developerTitle": "Enable Developer Mode: ",
@@ -1148,7 +1152,40 @@
"user.settings.integrations.incomingWebhooksDescription": "Manage your incoming webhooks", "user.settings.integrations.incomingWebhooksDescription": "Manage your incoming webhooks",
"user.settings.integrations.outWebhooks": "Outgoing Webhooks", "user.settings.integrations.outWebhooks": "Outgoing Webhooks",
"user.settings.integrations.outWebhooksDescription": "Manage your outgoing webhooks", "user.settings.integrations.outWebhooksDescription": "Manage your outgoing webhooks",
"user.settings.integrations.commands": "Commands",
"user.settings.integrations.commandsDescription": "Manage your commands",
"user.settings.integrations.title": "Integration Settings", "user.settings.integrations.title": "Integration Settings",
"user.settings.cmds.trigger": "Trigger: ",
"user.settings.cmds.display_name": "Display Name: ",
"user.settings.cmds.username": "Username: ",
"user.settings.cmds.icon_url": "Icon URL: ",
"user.settings.cmds.auto_complete": "Auto Complete: ",
"user.settings.cmds.auto_complete_desc": "Auto Complete Description: ",
"user.settings.cmds.auto_complete_hint": "Auto Complete Hint: ",
"user.settings.cmds.request_type": "Request Type: ",
"user.settings.cmds.request_type_post": "POST",
"user.settings.cmds.request_type_get": "GET",
"user.settings.cmds.url": "URL: ",
"user.settings.cmds.token": "Token: ",
"user.settings.cmds.regen": "Regen Token",
"user.settings.cmds.none": "None",
"Existing commands": "Existing commands",
"user.settings.cmds.add_desc": "Create commands to send message events to an external integration. Please see <a href=\"http://mattermost.org/commands\">http://mattermost.org/commands</a> to learn more.",
"user.settings.cmds.add_display_name.placeholder": "Display Name",
"user.settings.cmds.existing": "Existing commands",
"user.settings.cmds.add_username.placeholder": "Username",
"user.settings.cmds.username_desc": "The username to use when overriding the post.",
"user.settings.cmds.icon_url_desc": "URL to an icon",
"user.settings.cmds.trigger_desc": "Word to trigger on",
"user.settings.cmds.add_trigger.placeholder": "Command trigger e.g. \"hello\" not including the slash",
"user.settings.cmds.auto_complete_desc_desc": "A short description of what this commands does",
"user.settings.cmds.auto_complete_help": "Show this command in autocomplete list.",
"user.settings.cmds.auto_complete_desc.placeholder": "A short description of what this commands does.",
"user.settings.cmds.auto_complete_hint.placeholder": "[zipcode]",
"user.settings.cmds.auto_complete_hint_desc": "List parameters to be passed to the command.",
"user.settings.cmds.request_type_desc": "Command request type issued to the callback URL.",
"user.settings.cmds.url_desc": "URL that will receive the HTTP POST or GET event",
"user.settings.cmds.url.placeholder": "Must start with http:// or https://",
"user.settings.modal.general": "General", "user.settings.modal.general": "General",
"user.settings.modal.security": "Security", "user.settings.modal.security": "Security",
"user.settings.modal.notifications": "Notifications", "user.settings.modal.notifications": "Notifications",

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

@@ -299,8 +299,6 @@
"admin.service.googleDescription": "Asigna una llave a este campo para habilitar la previsualización de videos de YouTube tomados de los enlaces que aparecen en los mensajes o comentarios. Las instrucciones de como obtener una llave está disponible en <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">https://www.youtube.com/watch?v=Im69kzhpR3I</a>. Al dejar este campo en blanco deshabilita la generación de previsualizaciones de videos de YouTube desde los enlaces.", "admin.service.googleDescription": "Asigna una llave a este campo para habilitar la previsualización de videos de YouTube tomados de los enlaces que aparecen en los mensajes o comentarios. Las instrucciones de como obtener una llave está disponible en <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">https://www.youtube.com/watch?v=Im69kzhpR3I</a>. Al dejar este campo en blanco deshabilita la generación de previsualizaciones de videos de YouTube desde los enlaces.",
"admin.service.googleExample": "Ej \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"", "admin.service.googleExample": "Ej \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"",
"admin.service.googleTitle": "Llave de desarrolador Google:", "admin.service.googleTitle": "Llave de desarrolador Google:",
"admin.service.iconDescription": "Cuando es verdadero, se le permitirá cambiar el icono del mensaje desde webhooks. Nota, en combinación con permitir el cambio de nombre de usuario, podría exponer a los usuarios a sufrir ataques de phishing.",
"admin.service.iconTitle": "Habilitar el cambio de icono desde los Webhooks: ",
"admin.service.listenAddress": "Dirección de escucha:", "admin.service.listenAddress": "Dirección de escucha:",
"admin.service.listenDescription": "La dirección a la que se unirá y escuchará. Ingresar \":8065\" se podrá unir a todas las interfaces o podrá seleccionar una como ej: \"127.0.0.1:8065\". Cambiando este valor es necesario reiniciar el servidor.", "admin.service.listenDescription": "La dirección a la que se unirá y escuchará. Ingresar \":8065\" se podrá unir a todas las interfaces o podrá seleccionar una como ej: \"127.0.0.1:8065\". Cambiando este valor es necesario reiniciar el servidor.",
"admin.service.listenExample": "Ej \":8065\"", "admin.service.listenExample": "Ej \":8065\"",
@@ -308,8 +306,6 @@
"admin.service.mobileSessionDaysDesc": "La sesión nativa de los dispositivos moviles expirará luego de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.", "admin.service.mobileSessionDaysDesc": "La sesión nativa de los dispositivos moviles expirará luego de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.",
"admin.service.outWebhooksDesc": "Cuando es verdadero, los webhooks de salida serán permitidos.", "admin.service.outWebhooksDesc": "Cuando es verdadero, los webhooks de salida serán permitidos.",
"admin.service.outWebhooksTitle": "Habilitar Webhooks de Salida: ", "admin.service.outWebhooksTitle": "Habilitar Webhooks de Salida: ",
"admin.service.overrideDescription": "Cuando es verdadero, se le permitirá cambiar el nombre de usuario desde webhooks. Nota, en conjunto con cambio de icono, podría exponer a los usuarios a sufrir ataques de phishing.",
"admin.service.overrideTitle": "Habilitar el cambio de nombres de usuario desde los Webhooks: ",
"admin.service.save": "Guardar", "admin.service.save": "Guardar",
"admin.service.saving": "Guardando....", "admin.service.saving": "Guardando....",
"admin.service.securityDesc": "Cuando es verdadero, Los Administradores del Sistema serán notificados por correo electrónico se han anunciado alertas de seguridad relevantes en las últimas 12 horas. Requiere que los correos estén habilitados.", "admin.service.securityDesc": "Cuando es verdadero, Los Administradores del Sistema serán notificados por correo electrónico se han anunciado alertas de seguridad relevantes en las últimas 12 horas. Requiere que los correos estén habilitados.",
@@ -571,8 +567,8 @@
"claim.email_to_sso.pwd": "Contraseña", "claim.email_to_sso.pwd": "Contraseña",
"claim.email_to_sso.pwdError": "Por favor introduce tu contraseña.", "claim.email_to_sso.pwdError": "Por favor introduce tu contraseña.",
"claim.email_to_sso.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO", "claim.email_to_sso.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO",
"claim.email_to_sso.switchTo": "Cambiar cuenta a ", "claim.email_to_sso.switchTo": "Cambiar cuenta a {uiType}",
"claim.email_to_sso.title": "Cambiar Cuenta de Correo/Contraseña a ", "claim.email_to_sso.title": "Cambiar Cuenta de Correo/Contraseña a {uiType}",
"claim.sso_to_email.confirm": "Confirmar Contraseña", "claim.sso_to_email.confirm": "Confirmar Contraseña",
"claim.sso_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.", "claim.sso_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.",
"claim.sso_to_email.enterPwd": "Por favor ingresa una contraseña.", "claim.sso_to_email.enterPwd": "Por favor ingresa una contraseña.",

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

@@ -194,6 +194,10 @@ func TestIncomingWebhook(t *testing.T) {
user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User) user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(user.Id)) store.Must(api.Srv.Store.User().VerifyEmail(user.Id))
c := &api.Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
api.UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
ApiClient.LoginByEmail(team.Name, user.Email, "pwd") ApiClient.LoginByEmail(team.Name, user.Email, "pwd")
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}