Merge branch 'master' into advanced-permissions-phase-1

Этот коммит содержится в:
George Goldberg
2018-03-02 15:55:03 +00:00
родитель 21afaf4bed 2b3b6051d2
Коммит 901acc9703
74 изменённых файлов: 2713 добавлений и 1162 удалений

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

@@ -198,6 +198,10 @@ func (c *Client4) GetTestEmailRoute() string {
return fmt.Sprintf("/email/test")
}
func (c *Client4) GetTestS3Route() string {
return fmt.Sprintf("/file/s3_test")
}
func (c *Client4) GetDatabaseRoute() string {
return fmt.Sprintf("/database")
}
@@ -1919,7 +1923,8 @@ func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) {
// File Section
// UploadFile will upload a file to a channel, to be later attached to a post.
// UploadFile will upload a file to a channel using a multipart request, to be later attached to a post.
// This method is functionally equivalent to Client4.UploadFileAsRequestBody.
func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
@@ -1943,6 +1948,12 @@ func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*F
return c.DoUploadFile(c.GetFilesRoute(), body.Bytes(), writer.FormDataContentType())
}
// UploadFileAsRequestBody will upload a file to a channel as the body of a request, to be later attached
// to a post. This method is functionally equivalent to Client4.UploadFile.
func (c *Client4) UploadFileAsRequestBody(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) {
return c.DoUploadFile(c.GetFilesRoute()+fmt.Sprintf("?channel_id=%v&filename=%v", url.QueryEscape(channelId), url.QueryEscape(filename)), data, http.DetectContentType(data))
}
// GetFile gets the bytes for a file by id.
func (c *Client4) GetFile(fileId string) ([]byte, *Response) {
if r, err := c.DoApiGet(c.GetFileRoute(fileId), ""); err != nil {
@@ -2089,6 +2100,16 @@ func (c *Client4) TestEmail() (bool, *Response) {
}
}
// TestS3Connection will attempt to connect to the AWS S3.
func (c *Client4) TestS3Connection(config *Config) (bool, *Response) {
if r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson()); err != nil {
return false, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
}
// GetConfig will retrieve the server config with some sanitized items.
func (c *Client4) GetConfig() (*Config, *Response) {
if r, err := c.DoApiGet(c.GetConfigRoute(), ""); err != nil {
@@ -3343,3 +3364,56 @@ func (c *Client4) DeactivatePlugin(id string) (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r)
}
}
// SetTeamIcon sets team icon of the team
func (c *Client4) SetTeamIcon(teamId string, data []byte) (bool, *Response) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
if part, err := writer.CreateFormFile("image", "teamIcon.png"); err != nil {
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
} else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
}
if err := writer.Close(); err != nil {
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, err.Error(), http.StatusBadRequest)}
}
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetTeamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
rq.Header.Set("Content-Type", writer.FormDataContentType())
rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil {
// set to http.StatusForbidden(403)
return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetTeamRoute(teamId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)}
} else {
defer closeBody(rp)
if rp.StatusCode >= 300 {
return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body))
} else {
return CheckStatusOK(rp), BuildResponse(rp)
}
}
}
// GetTeamIcon gets the team icon of the team
func (c *Client4) GetTeamIcon(teamId, etag string) ([]byte, *Response) {
if r, err := c.DoApiGet(c.GetTeamRoute(teamId)+"/image", etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
if data, err := ioutil.ReadAll(r.Body); err != nil {
return nil, BuildErrorResponse(r, NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, err.Error(), r.StatusCode))
} else {
return data, BuildResponse(r)
}
}
}

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

@@ -165,6 +165,7 @@ const (
type ServiceSettings struct {
SiteURL *string
WebsocketURL *string
LicenseFileLocation *string
ListenAddress *string
ConnectionSecurity *string
@@ -196,6 +197,7 @@ type ServiceSettings struct {
EnforceMultifactorAuthentication *bool
EnableUserAccessTokens *bool
AllowCorsFrom *string
AllowCookiesForSubdomains *bool
SessionLengthWebInDays *int
SessionLengthMobileInDays *int
SessionLengthSSOInDays *int
@@ -232,6 +234,10 @@ func (s *ServiceSettings) SetDefaults() {
s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL)
}
if s.WebsocketURL == nil {
s.WebsocketURL = NewString("")
}
if s.LicenseFileLocation == nil {
s.LicenseFileLocation = NewString("")
}
@@ -388,6 +394,10 @@ func (s *ServiceSettings) SetDefaults() {
s.AllowCorsFrom = NewString(SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM)
}
if s.AllowCookiesForSubdomains == nil {
s.AllowCookiesForSubdomains = NewBool(false)
}
if s.WebserverMode == nil {
s.WebserverMode = NewString("gzip")
} else if *s.WebserverMode == "regular" {
@@ -1782,6 +1792,10 @@ func (o *Config) IsValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "", http.StatusBadRequest)
}
if len(*o.ServiceSettings.SiteURL) == 0 && *o.ServiceSettings.AllowCookiesForSubdomains {
return NewAppError("Config.IsValid", "Allowing cookies for subdomains requires SiteURL to be set.", nil, "", http.StatusBadRequest)
}
if err := o.TeamSettings.isValid(); err != nil {
return err
}
@@ -2089,6 +2103,12 @@ func (ss *ServiceSettings) isValid() *AppError {
}
}
if len(*ss.WebsocketURL) != 0 {
if _, err := url.ParseRequestURI(*ss.WebsocketURL); err != nil {
return NewAppError("Config.IsValid", "model.config.is_valid.websocket_url.app_error", nil, "", http.StatusBadRequest)
}
}
if len(*ss.ListenAddress) == 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
}

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

@@ -5,7 +5,6 @@ package model
import (
"fmt"
"sync"
"time"
)
@@ -15,89 +14,57 @@ type ScheduledTask struct {
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function TaskFunc
timer *time.Timer
}
var taskMutex = sync.Mutex{}
var tasks = make(map[string]*ScheduledTask)
func addTask(task *ScheduledTask) {
taskMutex.Lock()
defer taskMutex.Unlock()
tasks[task.Name] = task
}
func removeTaskByName(name string) {
taskMutex.Lock()
defer taskMutex.Unlock()
delete(tasks, name)
}
func GetTaskByName(name string) *ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
if task, ok := tasks[name]; ok {
return task
}
return nil
}
func GetAllTasks() *map[string]*ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
return &tasks
function func()
cancel chan struct{}
cancelled chan struct{}
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: timeToExecution,
Recurring: false,
function: function,
}
taskRunner := func() {
go task.function()
removeTaskByName(task.Name)
}
task.timer = time.AfterFunc(timeToExecution, taskRunner)
addTask(task)
return task
return createTask(name, function, timeToExecution, false)
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
return createTask(name, function, interval, true)
}
func createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: true,
Recurring: recurring,
function: function,
cancel: make(chan struct{}),
cancelled: make(chan struct{}),
}
taskRecurer := func() {
go task.function()
task.timer.Reset(task.Interval)
}
go func() {
defer close(task.cancelled)
task.timer = time.AfterFunc(interval, taskRecurer)
ticker := time.NewTicker(interval)
defer func() {
ticker.Stop()
}()
addTask(task)
for {
select {
case <-ticker.C:
function()
case <-task.cancel:
return
}
if !task.Recurring {
break
}
}
}()
return task
}
func (task *ScheduledTask) Cancel() {
task.timer.Stop()
removeTaskByName(task.Name)
}
// Executes the task immediatly. A recurring task will be run regularally after interval.
func (task *ScheduledTask) Execute() {
task.function()
task.timer.Reset(task.Interval)
close(task.cancel)
<-task.cancelled
}
func (task *ScheduledTask) String() string {

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

@@ -4,185 +4,72 @@
package model
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCreateTask(t *testing.T) {
TASK_NAME := "Test Task"
TASK_TIME := time.Second * 3
TASK_TIME := time.Second * 2
testValue := 0
executionCount := new(int32)
testFunc := func() {
testValue = 1
atomic.AddInt32(executionCount, 1)
}
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
time.Sleep(TASK_TIME + time.Second)
if testValue != 1 {
t.Fatal("Task did not execute")
}
if task.Name != TASK_NAME {
t.Fatal("Bad name")
}
if task.Interval != TASK_TIME {
t.Fatal("Bad interval")
}
if task.Recurring {
t.Fatal("should not reccur")
}
assert.EqualValues(t, 1, atomic.LoadInt32(executionCount))
assert.Equal(t, TASK_NAME, task.Name)
assert.Equal(t, TASK_TIME, task.Interval)
assert.False(t, task.Recurring)
}
func TestCreateRecurringTask(t *testing.T) {
TASK_NAME := "Test Recurring Task"
TASK_TIME := time.Second * 3
TASK_TIME := time.Second * 2
testValue := 0
executionCount := new(int32)
testFunc := func() {
testValue += 1
atomic.AddInt32(executionCount, 1)
}
task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
time.Sleep(TASK_TIME + time.Second)
if testValue != 1 {
t.Fatal("Task did not execute")
}
assert.EqualValues(t, 1, atomic.LoadInt32(executionCount))
time.Sleep(TASK_TIME)
if testValue != 2 {
t.Fatal("Task did not re-execute")
}
if task.Name != TASK_NAME {
t.Fatal("Bad name")
}
if task.Interval != TASK_TIME {
t.Fatal("Bad interval")
}
if !task.Recurring {
t.Fatal("should reccur")
}
assert.EqualValues(t, 2, atomic.LoadInt32(executionCount))
assert.Equal(t, TASK_NAME, task.Name)
assert.Equal(t, TASK_TIME, task.Interval)
assert.True(t, task.Recurring)
task.Cancel()
}
func TestCancelTask(t *testing.T) {
TASK_NAME := "Test Task"
TASK_TIME := time.Second * 3
TASK_TIME := time.Second
testValue := 0
executionCount := new(int32)
testFunc := func() {
testValue = 1
atomic.AddInt32(executionCount, 1)
}
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
task.Cancel()
time.Sleep(TASK_TIME + time.Second)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
}
func TestGetAllTasks(t *testing.T) {
doNothing := func() {}
CreateTask("Task1", doNothing, time.Hour)
CreateTask("Task2", doNothing, time.Second)
CreateRecurringTask("Task3", doNothing, time.Second)
task4 := CreateRecurringTask("Task4", doNothing, time.Second)
task4.Cancel()
time.Sleep(time.Second * 3)
tasks := *GetAllTasks()
if len(tasks) != 2 {
t.Fatal("Wrong number of tasks got: ", len(tasks))
}
for _, task := range tasks {
if task.Name != "Task1" && task.Name != "Task3" {
t.Fatal("Wrong tasks")
}
}
}
func TestExecuteTask(t *testing.T) {
TASK_NAME := "Test Task"
TASK_TIME := time.Second * 5
testValue := 0
testFunc := func() {
testValue += 1
}
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
task.Execute()
if testValue != 1 {
t.Fatal("Task did not execute")
}
time.Sleep(TASK_TIME + time.Second)
if testValue != 2 {
t.Fatal("Task re-executed")
}
}
func TestExecuteTaskRecurring(t *testing.T) {
TASK_NAME := "Test Recurring Task"
TASK_TIME := time.Second * 5
testValue := 0
testFunc := func() {
testValue += 1
}
task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME)
if testValue != 0 {
t.Fatal("Unexpected execuition of task")
}
time.Sleep(time.Second * 3)
task.Execute()
if testValue != 1 {
t.Fatal("Task did not execute")
}
time.Sleep(time.Second * 3)
if testValue != 1 {
t.Fatal("Task should not have executed before 5 seconds")
}
time.Sleep(time.Second * 3)
if testValue != 2 {
t.Fatal("Task did not re-execute after forced execution")
}
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
}

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

@@ -26,19 +26,20 @@ const (
)
type Team struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Description string `json:"description"`
Email string `json:"email"`
Type string `json:"type"`
CompanyName string `json:"company_name"`
AllowedDomains string `json:"allowed_domains"`
InviteId string `json:"invite_id"`
AllowOpenInvite bool `json:"allow_open_invite"`
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Description string `json:"description"`
Email string `json:"email"`
Type string `json:"type"`
CompanyName string `json:"company_name"`
AllowedDomains string `json:"allowed_domains"`
InviteId string `json:"invite_id"`
AllowOpenInvite bool `json:"allow_open_invite"`
LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"`
}
type TeamPatch struct {

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

@@ -13,6 +13,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
"4.7.1",
"4.7.0",
"4.6.0",
"4.5.0",

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

@@ -5,6 +5,7 @@ package model
import (
"encoding/json"
"fmt"
"io"
)
@@ -59,11 +60,32 @@ type WebsocketBroadcast struct {
TeamId string `json:"team_id"` // broadcast only occurs for users in this team
}
type precomputedWebSocketEventJSON struct {
Event json.RawMessage
Data json.RawMessage
Broadcast json.RawMessage
}
type WebSocketEvent struct {
Event string `json:"event"`
Data map[string]interface{} `json:"data"`
Broadcast *WebsocketBroadcast `json:"broadcast"`
Sequence int64 `json:"seq"`
precomputedJSON *precomputedWebSocketEventJSON
}
// PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence.
// This makes ToJson much more efficient when sending the same event to multiple connections.
func (m *WebSocketEvent) PrecomputeJSON() {
event, _ := json.Marshal(m.Event)
data, _ := json.Marshal(m.Data)
broadcast, _ := json.Marshal(m.Broadcast)
m.precomputedJSON = &precomputedWebSocketEventJSON{
Event: json.RawMessage(event),
Data: json.RawMessage(data),
Broadcast: json.RawMessage(broadcast),
}
}
func (m *WebSocketEvent) Add(key string, value interface{}) {
@@ -84,6 +106,9 @@ func (o *WebSocketEvent) EventType() string {
}
func (o *WebSocketEvent) ToJson() string {
if o.precomputedJSON != nil {
return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, o.precomputedJSON.Event, o.precomputedJSON.Data, o.precomputedJSON.Broadcast, o.Sequence)
}
b, _ := json.Marshal(o)
return string(b)
}

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

@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWebSocketEvent(t *testing.T) {
@@ -54,3 +56,49 @@ func TestWebSocketResponse(t *testing.T) {
t.Fatal("Ids do not match")
}
}
func TestWebSocketEvent_PrecomputeJSON(t *testing.T) {
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
event.Sequence = 7
before := event.ToJson()
event.PrecomputeJSON()
after := event.ToJson()
assert.JSONEq(t, before, after)
}
var stringSink string
func BenchmarkWebSocketEvent_ToJson(b *testing.B) {
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
for i := 0; i < 100; i++ {
event.Data[NewId()] = NewId()
}
b.Run("SerializedNTimes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
stringSink = event.ToJson()
}
})
b.Run("PrecomputedNTimes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
event.PrecomputeJSON()
}
})
b.Run("PrecomputedAndSerializedNTimes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
event.PrecomputeJSON()
stringSink = event.ToJson()
}
})
event.PrecomputeJSON()
b.Run("PrecomputedOnceAndSerializedNTimes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
stringSink = event.ToJson()
}
})
}