diff options
Diffstat (limited to 'api')
52 files changed, 219 insertions, 1183 deletions
diff --git a/api/admin.go b/api/admin.go index cb1b7efde..a6ced71a9 100644 --- a/api/admin.go +++ b/api/admin.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/admin_test.go b/api/admin_test.go index dc569620e..ae7259863 100644 --- a/api/admin_test.go +++ b/api/admin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/api.go b/api/api.go index 8ec078dd2..c9c876b02 100644 --- a/api/api.go +++ b/api/api.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/apitestlib.go b/api/apitestlib.go index bcc7de879..af14ac431 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/channel.go b/api/channel.go index 0db3499e0..73daaf3d4 100644 --- a/api/channel.go +++ b/api/channel.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/channel_test.go b/api/channel_test.go index 23705f172..52212dac7 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/cli_test.go b/api/cli_test.go index 1f60b02cd..afdada7b3 100644 --- a/api/cli_test.go +++ b/api/cli_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command.go b/api/command.go index 75f37b9ff..aaaa790c0 100644 --- a/api/command.go +++ b/api/command.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_echo_test.go b/api/command_echo_test.go index 02583d2aa..56df94b0b 100644 --- a/api/command_echo_test.go +++ b/api/command_echo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_expand_collapse_test.go b/api/command_expand_collapse_test.go index bad5ef6c4..54873377c 100644 --- a/api/command_expand_collapse_test.go +++ b/api/command_expand_collapse_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_invite_people_test.go b/api/command_invite_people_test.go index d4f579c4d..0e8c3fe38 100644 --- a/api/command_invite_people_test.go +++ b/api/command_invite_people_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_join_test.go b/api/command_join_test.go index cce837ceb..c179175fb 100644 --- a/api/command_join_test.go +++ b/api/command_join_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_loadtest_test.go b/api/command_loadtest_test.go index 091e05831..a02f4f9d3 100644 --- a/api/command_loadtest_test.go +++ b/api/command_loadtest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_logout_test.go b/api/command_logout_test.go index d61b30633..ff20a3831 100644 --- a/api/command_logout_test.go +++ b/api/command_logout_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_me_test.go b/api/command_me_test.go index 0250cfb0f..1f49566fa 100644 --- a/api/command_me_test.go +++ b/api/command_me_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_msg_test.go b/api/command_msg_test.go index 4fe28fdba..2e2d927e3 100644 --- a/api/command_msg_test.go +++ b/api/command_msg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_shortcuts_test.go b/api/command_shortcuts_test.go index 049175ec9..ce5019049 100644 --- a/api/command_shortcuts_test.go +++ b/api/command_shortcuts_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go index e64f4c4b1..b265fbb25 100644 --- a/api/command_shrug_test.go +++ b/api/command_shrug_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_statuses_test.go b/api/command_statuses_test.go index 063d76062..a562ed882 100644 --- a/api/command_statuses_test.go +++ b/api/command_statuses_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_test.go b/api/command_test.go index 8194a4c60..6207e6cf5 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/context.go b/api/context.go index bc5855345..21bbb1e37 100644 --- a/api/context.go +++ b/api/context.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/context_test.go b/api/context_test.go index cd4d058cf..95a8459ff 100644 --- a/api/context_test.go +++ b/api/context_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/deprecated.go b/api/deprecated.go index eca6f78f8..1c1228793 100644 --- a/api/deprecated.go +++ b/api/deprecated.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/deprecated_test.go b/api/deprecated_test.go index b3249a58d..6943c6918 100644 --- a/api/deprecated_test.go +++ b/api/deprecated_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/emoji.go b/api/emoji.go index 2f94fb0e0..feb65877a 100644 --- a/api/emoji.go +++ b/api/emoji.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api @@ -8,10 +8,6 @@ import ( "image" "image/draw" "image/gif" - _ "image/jpeg" - "image/png" - "io" - "mime/multipart" "net/http" "strings" @@ -25,12 +21,6 @@ import ( "image/color/palette" ) -const ( - MaxEmojiFileSize = 1000 * 1024 // 1 MB - MaxEmojiWidth = 128 - MaxEmojiHeight = 128 -) - func InitEmoji() { l4g.Debug(utils.T("api.emoji.init.debug")) @@ -47,12 +37,12 @@ func getEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Emoji().GetAll(); result.Err != nil { - c.Err = result.Err + listEmoji, err := app.GetEmojiList() + if err != nil { + c.Err = err return } else { - emoji := result.Data.([]*model.Emoji) - w.Write([]byte(model.EmojiListToJson(emoji))) + w.Write([]byte(model.EmojiListToJson(listEmoji))) } } @@ -76,13 +66,13 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - if r.ContentLength > MaxEmojiFileSize { + if r.ContentLength > app.MaxEmojiFileSize { c.Err = model.NewLocAppError("createEmoji", "api.emoji.create.too_large.app_error", nil, "") c.Err.StatusCode = http.StatusRequestEntityTooLarge return } - if err := r.ParseMultipartForm(MaxEmojiFileSize); err != nil { + if err := r.ParseMultipartForm(app.MaxEmojiFileSize); err != nil { c.Err = model.NewLocAppError("createEmoji", "api.emoji.create.parse.app_error", nil, err.Error()) c.Err.StatusCode = http.StatusBadRequest return @@ -124,7 +114,7 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { if imageData := m.File["image"]; len(imageData) == 0 { c.SetInvalidParam("createEmoji", "image") return - } else if err := uploadEmojiImage(emoji.Id, imageData[0]); err != nil { + } else if err := app.UploadEmojiImage(emoji.Id, imageData[0]); err != nil { c.Err = err return } @@ -137,58 +127,6 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { } } -func uploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError { - file, err := imageData.Open() - if err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.open.app_error", nil, "") - } - defer file.Close() - - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - - // make sure the file is an image and is within the required dimensions - if config, _, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())); err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.image.app_error", nil, err.Error()) - } else if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight { - data := buf.Bytes() - newbuf := bytes.NewBuffer(nil) - if info, err := model.GetInfoForBytes(imageData.Filename, data); err != nil { - return err - } else if info.MimeType == "image/gif" { - if gif_data, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_decode_error", nil, "") - } else { - resized_gif := resizeEmojiGif(gif_data) - if err := gif.EncodeAll(newbuf, resized_gif); err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "") - } - if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { - return err - } - } - } else { - if img, _, err := image.Decode(bytes.NewReader(data)); err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.decode_error", nil, "") - } else { - resized_image := resizeEmoji(img, config.Width, config.Height) - if err := png.Encode(newbuf, resized_image); err != nil { - return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "") - } - if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { - return err - } - } - } - } else { - if err := app.WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil { - return err - } - } - - return nil -} - func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) { if !*utils.Cfg.ServiceSettings.EnableCustomEmoji { c.Err = model.NewLocAppError("deleteEmoji", "api.emoji.disabled.app_error", nil, "") @@ -210,41 +148,24 @@ func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - var emoji *model.Emoji - if result := <-app.Srv.Store.Emoji().Get(id, false); result.Err != nil { - c.Err = result.Err - return - } else { - emoji = result.Data.(*model.Emoji) - - if c.Session.UserId != emoji.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.Err = model.NewLocAppError("deleteEmoji", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId) - c.Err.StatusCode = http.StatusUnauthorized - return - } - } - - if err := (<-app.Srv.Store.Emoji().Delete(id, model.GetMillis())).Err; err != nil { + emoji, err := app.GetEmoji(id) + if err != nil { c.Err = err return } - go deleteEmojiImage(id) - go deleteReactionsForEmoji(emoji.Name) - - ReturnStatusOK(w) -} - -func deleteEmojiImage(id string) { - if err := app.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil { - l4g.Error("Failed to rename image when deleting emoji %v", id) + if c.Session.UserId != emoji.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.Err = model.NewLocAppError("deleteEmoji", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId) + c.Err.StatusCode = http.StatusUnauthorized + return } -} -func deleteReactionsForEmoji(emojiName string) { - if result := <-app.Srv.Store.Reaction().DeleteAllWithEmojiName(emojiName); result.Err != nil { - l4g.Warn(utils.T("api.emoji.delete.delete_reactions.app_error"), emojiName) - l4g.Warn(result.Err) + err = app.DeleteEmoji(emoji) + if err != nil { + c.Err = err + return + } else { + ReturnStatusOK(w) } } @@ -302,10 +223,10 @@ func resizeEmoji(img image.Image, width int, height int) image.Image { emojiHeight := float64(height) var emoji image.Image - if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth { + if emojiHeight <= app.MaxEmojiHeight && emojiWidth <= app.MaxEmojiWidth { emoji = img } else { - emoji = imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos) + emoji = imaging.Fit(img, app.MaxEmojiWidth, app.MaxEmojiHeight, imaging.Lanczos) } return emoji } diff --git a/api/emoji_test.go b/api/emoji_test.go index fb90d8781..600f7975e 100644 --- a/api/emoji_test.go +++ b/api/emoji_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api @@ -6,10 +6,7 @@ package api import ( "bytes" "image" - "image/color" "image/gif" - "image/jpeg" - "image/png" "testing" "time" @@ -113,14 +110,14 @@ func TestCreateEmoji(t *testing.T) { } // try to create an emoji when they're disabled - if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil { + if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { t.Fatal("shouldn't be able to create an emoji when they're disabled") } *utils.Cfg.ServiceSettings.EnableCustomEmoji = true // try to create a valid gif emoji when they're enabled - if emojiResult, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err != nil { + if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err != nil { t.Fatal(err) } else { emoji = emojiResult @@ -131,7 +128,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: emoji.Name, } - if _, err := Client.CreateEmoji(emoji2, createTestGif(t, 10, 10), "image.gif"); err == nil { + if _, err := Client.CreateEmoji(emoji2, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { t.Fatal("shouldn't be able to create an emoji with a duplicate name") } @@ -142,7 +139,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if emojiResult, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 10, 10, 10), "image.gif"); err != nil { + if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif"); err != nil { t.Fatal(err) } else { emoji = emojiResult @@ -154,7 +151,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if emojiResult, err := Client.CreateEmoji(emoji, createTestJpeg(t, 10, 10), "image.jpeg"); err != nil { + if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.jpeg"); err != nil { t.Fatal(err) } else { emoji = emojiResult @@ -166,7 +163,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if emojiResult, err := Client.CreateEmoji(emoji, createTestPng(t, 10, 10), "image.png"); err != nil { + if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.png"); err != nil { t.Fatal(err) } else { emoji = emojiResult @@ -178,7 +175,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if _, err := Client.CreateEmoji(emoji, createTestGif(t, 1000, 10), "image.gif"); err != nil { + if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif"); err != nil { t.Fatal("should be able to create an emoji that's too wide by resizing it") } @@ -187,7 +184,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 1000), "image.gif"); err != nil { + if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 1000), "image.gif"); err != nil { t.Fatal("should be able to create an emoji that's too tall by resizing it") } @@ -196,7 +193,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - if _, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil { + if _, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil { t.Fatal("shouldn't be able to create an emoji that's too large") } @@ -214,7 +211,7 @@ func TestCreateEmoji(t *testing.T) { CreatorId: th.BasicUser2.Id, Name: model.NewId(), } - if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil { + if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { t.Fatal("shouldn't be able to create an emoji as another user") } } @@ -232,7 +229,7 @@ func TestDeleteEmoji(t *testing.T) { emoji1 := createTestEmoji(t, &model.Emoji{ CreatorId: th.BasicUser.Id, Name: model.NewId(), - }, createTestGif(t, 10, 10)) + }, utils.CreateTestGif(t, 10, 10)) if _, err := Client.DeleteEmoji(emoji1.Id); err == nil { t.Fatal("shouldn't have been able to delete an emoji when they're disabled") @@ -253,7 +250,7 @@ func TestDeleteEmoji(t *testing.T) { emoji2 := createTestEmoji(t, &model.Emoji{ CreatorId: th.BasicUser2.Id, Name: model.NewId(), - }, createTestGif(t, 10, 10)) + }, utils.CreateTestGif(t, 10, 10)) if _, err := Client.DeleteEmoji(emoji2.Id); err == nil { t.Fatal("shouldn't be able to delete another user's emoji") @@ -266,54 +263,6 @@ func TestDeleteEmoji(t *testing.T) { } } -func createTestGif(t *testing.T, width int, height int) []byte { - var buffer bytes.Buffer - - if err := gif.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height)), nil); err != nil { - t.Fatalf("failed to create gif: %v", err.Error()) - } - - return buffer.Bytes() -} - -func createTestAnimatedGif(t *testing.T, width int, height int, frames int) []byte { - var buffer bytes.Buffer - - img := gif.GIF{ - Image: make([]*image.Paletted, frames, frames), - Delay: make([]int, frames, frames), - } - for i := 0; i < frames; i++ { - img.Image[i] = image.NewPaletted(image.Rect(0, 0, width, height), color.Palette{color.Black}) - img.Delay[i] = 0 - } - if err := gif.EncodeAll(&buffer, &img); err != nil { - t.Fatalf("failed to create animated gif: %v", err.Error()) - } - - return buffer.Bytes() -} - -func createTestJpeg(t *testing.T, width int, height int) []byte { - var buffer bytes.Buffer - - if err := jpeg.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height)), nil); err != nil { - t.Fatalf("failed to create jpeg: %v", err.Error()) - } - - return buffer.Bytes() -} - -func createTestPng(t *testing.T, width int, height int) []byte { - var buffer bytes.Buffer - - if err := png.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height))); err != nil { - t.Fatalf("failed to create png: %v", err.Error()) - } - - return buffer.Bytes() -} - func createTestEmoji(t *testing.T, emoji *model.Emoji, imageData []byte) *model.Emoji { emoji = store.Must(app.Srv.Store.Emoji().Save(emoji)).(*model.Emoji) @@ -342,7 +291,7 @@ func TestGetEmojiImage(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - emoji1 = Client.MustGeneric(Client.CreateEmoji(emoji1, createTestGif(t, 10, 10), "image.gif")).(*model.Emoji) + emoji1 = Client.MustGeneric(Client.CreateEmoji(emoji1, utils.CreateTestGif(t, 10, 10), "image.gif")).(*model.Emoji) defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji1.Id)) }() *utils.Cfg.ServiceSettings.EnableCustomEmoji = false @@ -367,7 +316,7 @@ func TestGetEmojiImage(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - emoji2 = Client.MustGeneric(Client.CreateEmoji(emoji2, createTestAnimatedGif(t, 10, 10, 10), "image.gif")).(*model.Emoji) + emoji2 = Client.MustGeneric(Client.CreateEmoji(emoji2, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")).(*model.Emoji) defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji2.Id)) }() if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji2.Id), "", ""); err != nil { @@ -384,7 +333,7 @@ func TestGetEmojiImage(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - emoji3 = Client.MustGeneric(Client.CreateEmoji(emoji3, createTestJpeg(t, 10, 10), "image.jpeg")).(*model.Emoji) + emoji3 = Client.MustGeneric(Client.CreateEmoji(emoji3, utils.CreateTestJpeg(t, 10, 10), "image.jpeg")).(*model.Emoji) defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji3.Id)) }() if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji3.Id), "", ""); err != nil { @@ -401,7 +350,7 @@ func TestGetEmojiImage(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - emoji4 = Client.MustGeneric(Client.CreateEmoji(emoji4, createTestPng(t, 10, 10), "image.png")).(*model.Emoji) + emoji4 = Client.MustGeneric(Client.CreateEmoji(emoji4, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji) defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji4.Id)) }() if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji4.Id), "", ""); err != nil { @@ -418,7 +367,7 @@ func TestGetEmojiImage(t *testing.T) { CreatorId: th.BasicUser.Id, Name: model.NewId(), } - emoji5 = Client.MustGeneric(Client.CreateEmoji(emoji5, createTestPng(t, 10, 10), "image.png")).(*model.Emoji) + emoji5 = Client.MustGeneric(Client.CreateEmoji(emoji5, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji) Client.MustGeneric(Client.DeleteEmoji(emoji5.Id)) if _, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji5.Id), "", ""); err == nil { @@ -428,12 +377,12 @@ func TestGetEmojiImage(t *testing.T) { func TestResizeEmoji(t *testing.T) { // try to resize a jpeg image within MaxEmojiWidth and MaxEmojiHeight - small_img_data := createTestJpeg(t, MaxEmojiWidth, MaxEmojiHeight) + small_img_data := utils.CreateTestJpeg(t, app.MaxEmojiWidth, app.MaxEmojiHeight) if small_img, _, err := image.Decode(bytes.NewReader(small_img_data)); err != nil { t.Fatal("failed to decode jpeg bytes to image.Image") } else { resized_img := resizeEmoji(small_img, small_img.Bounds().Dx(), small_img.Bounds().Dy()) - if resized_img.Bounds().Dx() > MaxEmojiWidth || resized_img.Bounds().Dy() > MaxEmojiHeight { + if resized_img.Bounds().Dx() > app.MaxEmojiWidth || resized_img.Bounds().Dy() > app.MaxEmojiHeight { t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") } if resized_img != small_img { @@ -441,32 +390,32 @@ func TestResizeEmoji(t *testing.T) { } } // try to resize a jpeg image - jpeg_data := createTestJpeg(t, 256, 256) + jpeg_data := utils.CreateTestJpeg(t, 256, 256) if jpeg_img, _, err := image.Decode(bytes.NewReader(jpeg_data)); err != nil { t.Fatal("failed to decode jpeg bytes to image.Image") } else { resized_jpeg := resizeEmoji(jpeg_img, jpeg_img.Bounds().Dx(), jpeg_img.Bounds().Dy()) - if resized_jpeg.Bounds().Dx() > MaxEmojiWidth || resized_jpeg.Bounds().Dy() > MaxEmojiHeight { + if resized_jpeg.Bounds().Dx() > app.MaxEmojiWidth || resized_jpeg.Bounds().Dy() > app.MaxEmojiHeight { t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") } } // try to resize a png image - png_data := createTestJpeg(t, 256, 256) + png_data := utils.CreateTestJpeg(t, 256, 256) if png_img, _, err := image.Decode(bytes.NewReader(png_data)); err != nil { t.Fatal("failed to decode png bytes to image.Image") } else { resized_png := resizeEmoji(png_img, png_img.Bounds().Dx(), png_img.Bounds().Dy()) - if resized_png.Bounds().Dx() > MaxEmojiWidth || resized_png.Bounds().Dy() > MaxEmojiHeight { + if resized_png.Bounds().Dx() > app.MaxEmojiWidth || resized_png.Bounds().Dy() > app.MaxEmojiHeight { t.Fatal("resized png width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") } } // try to resize an animated gif - gif_data := createTestAnimatedGif(t, 256, 256, 10) + gif_data := utils.CreateTestAnimatedGif(t, 256, 256, 10) if gif_img, err := gif.DecodeAll(bytes.NewReader(gif_data)); err != nil { t.Fatal("failed to decode gif bytes to gif.GIF") } else { resized_gif := resizeEmojiGif(gif_img) - if resized_gif.Config.Width > MaxEmojiWidth || resized_gif.Config.Height > MaxEmojiHeight { + if resized_gif.Config.Width > app.MaxEmojiWidth || resized_gif.Config.Height > app.MaxEmojiHeight { t.Fatal("resized gif width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") } if len(resized_gif.Image) != len(gif_img.Image) { diff --git a/api/file.go b/api/file.go index 54df78dd7..0f2fd9319 100644 --- a/api/file.go +++ b/api/file.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/file_test.go b/api/file_test.go index c004bb562..1e65c33e8 100644 --- a/api/file_test.go +++ b/api/file_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/general.go b/api/general.go index e273268a4..16a739704 100644 --- a/api/general.go +++ b/api/general.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/general_test.go b/api/general_test.go index 0cc0f120f..51593ab9e 100644 --- a/api/general_test.go +++ b/api/general_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/license.go b/api/license.go index ea5de20d4..7a9e57677 100644 --- a/api/license.go +++ b/api/license.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/license_test.go b/api/license_test.go index c5fffd6e9..978e044cc 100644 --- a/api/license_test.go +++ b/api/license_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/oauth.go b/api/oauth.go index 1e3dd89b8..fa076c56e 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -1,25 +1,17 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api import ( - "crypto/tls" - b64 "encoding/base64" - "fmt" - "io" - "io/ioutil" "net/http" "net/url" - "strconv" "strings" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/app" - "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -48,15 +40,8 @@ func InitOAuth() { } func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("registerOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } @@ -67,72 +52,50 @@ func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { return } - secret := model.NewId() - - oauthApp.ClientSecret = secret oauthApp.CreatorId = c.Session.UserId - if result := <-app.Srv.Store.OAuth().SaveApp(oauthApp); result.Err != nil { - c.Err = result.Err - return - } else { - oauthApp = result.Data.(*model.OAuthApp) + rapp, err := app.CreateOAuthApp(oauthApp) - c.LogAudit("client_id=" + oauthApp.Id) - - w.Write([]byte(oauthApp.ToJson())) + if err != nil { + c.Err = err return } + c.LogAudit("client_id=" + rapp.Id) + w.Write([]byte(rapp.ToJson())) } func getOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getOAuthAppsByUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } - var ochan store.StoreChannel + var apps []*model.OAuthApp + var err *model.AppError if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - ochan = app.Srv.Store.OAuth().GetApps() + apps, err = app.GetOAuthApps(0, 100000) } else { - c.Err = nil - ochan = app.Srv.Store.OAuth().GetAppByUser(c.Session.UserId) + apps, err = app.GetOAuthAppsByCreator(c.Session.UserId, 0, 100000) } - if result := <-ochan; result.Err != nil { - c.Err = result.Err + if err != nil { + c.Err = err return - } else { - apps := result.Data.([]*model.OAuthApp) - w.Write([]byte(model.OAuthAppListToJson(apps))) } + + w.Write([]byte(model.OAuthAppListToJson(apps))) } func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getOAuthAppInfo", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) - clientId := params["client_id"] - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil { - c.Err = model.NewLocAppError("getOAuthAppInfo", "api.oauth.allow_oauth.database.app_error", nil, "") + oauthApp, err := app.GetOAuthApp(clientId) + + if err != nil { + c.Err = err return - } else { - oauthApp = result.Data.(*model.OAuthApp) } oauthApp.Sanitize() @@ -140,123 +103,49 @@ func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { } func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - c.LogAudit("attempt") - - responseData := map[string]string{} - responseType := r.URL.Query().Get("response_type") if len(responseType) == 0 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_response.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_response.app_error", nil, "", http.StatusBadRequest) return } clientId := r.URL.Query().Get("client_id") if len(clientId) != 26 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_client.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_client.app_error", nil, "", http.StatusBadRequest) return } redirectUri := r.URL.Query().Get("redirect_uri") if len(redirectUri) == 0 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_redirect.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_redirect.app_error", nil, "", http.StatusBadRequest) return } scope := r.URL.Query().Get("scope") state := r.URL.Query().Get("state") - if len(scope) == 0 { - scope = model.DEFAULT_SCOPE - } - - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.database.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - } - - if !oauthApp.IsValidRedirectURL(redirectUri) { - c.LogAudit("fail - redirect_uri did not match registered callback") - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if responseType != model.AUTHCODE_RESPONSE_TYPE { - responseData["redirect"] = redirectUri + "?error=unsupported_response_type&state=" + state - w.Write([]byte(model.MapToJson(responseData))) - return - } - - authData := &model.AuthData{UserId: c.Session.UserId, ClientId: clientId, CreateAt: model.GetMillis(), RedirectUri: redirectUri, State: state, Scope: scope} - authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, c.Session.UserId)) - - // this saves the OAuth2 app as authorized - authorizedApp := model.Preference{ - UserId: c.Session.UserId, - Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, - Name: clientId, - Value: scope, - } + c.LogAudit("attempt") - if result := <-app.Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil { - responseData["redirect"] = redirectUri + "?error=server_error&state=" + state - w.Write([]byte(model.MapToJson(responseData))) - return - } + redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, responseType, clientId, redirectUri, scope, state) - if result := <-app.Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil { - responseData["redirect"] = redirectUri + "?error=server_error&state=" + state - w.Write([]byte(model.MapToJson(responseData))) + if err != nil { + c.Err = err return } - c.LogAudit("success") + c.LogAudit("") - responseData["redirect"] = redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State) - w.Write([]byte(model.MapToJson(responseData))) + w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl}))) } func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getAuthorizedApps", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - ochan := app.Srv.Store.OAuth().GetAuthorizedApps(c.Session.UserId) - if result := <-ochan; result.Err != nil { - c.Err = result.Err + apps, err := app.GetAuthorizedAppsForUser(c.Session.UserId, 0, 10000) + if err != nil { + c.Err = err return - } else { - apps := result.Data.([]*model.OAuthApp) - for k, a := range apps { - a.Sanitize() - apps[k] = a - } - - w.Write([]byte(model.OAuthAppListToJson(apps))) } -} -func GetAuthData(code string) *model.AuthData { - if result := <-app.Srv.Store.OAuth().GetAuthData(code); result.Err != nil { - l4g.Error(utils.T("api.oauth.get_auth_data.find.error"), code) - return nil - } else { - return result.Data.(*model.AuthData) - } + w.Write([]byte(model.OAuthAppListToJson(apps))) } func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { @@ -273,60 +162,36 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" - if body, teamId, props, err := AuthorizeOAuthUser(service, code, state, uri); err != nil { + body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri) + if err != nil { c.Err = err return + } + + user, err := app.CompleteOAuth(service, body, teamId, props) + if err != nil { + c.Err = err + return + } + + action := props["action"] + + var redirectUrl string + if action == model.OAUTH_ACTION_EMAIL_TO_SSO { + redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change" + } else if action == model.OAUTH_ACTION_SSO_TO_EMAIL { + + redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"]) } else { - defer func() { - ioutil.ReadAll(body) - body.Close() - }() - - action := props["action"] - switch action { - case model.OAUTH_ACTION_SIGNUP: - if user, err := app.CreateOAuthUser(service, body, teamId); err != nil { - c.Err = err - } else { - doLogin(c, w, r, user, "") - } - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_LOGIN: - user := LoginByOAuth(c, w, r, service, body) - if len(teamId) > 0 { - c.Err = app.AddUserToTeamByTeamId(teamId, user) - } - if c.Err == nil { - if val, ok := props["redirect_to"]; ok { - http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusTemporaryRedirect) - return - } - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_EMAIL_TO_SSO: - CompleteSwitchWithOAuth(c, w, r, service, body, props["email"]) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/login?extra=signin_change", http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_SSO_TO_EMAIL: - LoginByOAuth(c, w, r, service, body) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect) - } - break - default: - LoginByOAuth(c, w, r, service, body) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break + doLogin(c, w, r, user, "") + if c.Err != nil { + return } + + redirectUrl = c.GetSiteURLHeader() } + + http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) } func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { @@ -373,42 +238,15 @@ func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { // Automatically allow if the app is trusted if oauthApp.IsTrusted || isAuthorized { - closeBody := func(r *http.Response) { - if r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } - } + redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, model.AUTHCODE_RESPONSE_TYPE, clientId, redirect, scope, state) - doAllow := func() (*http.Response, *model.AppError) { - HttpClient := &http.Client{} - url := c.GetSiteURLHeader() + "/api/v3/oauth/allow?response_type=" + model.AUTHCODE_RESPONSE_TYPE + "&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirect) + "&scope=" + scope + "&state=" + url.QueryEscape(state) - rq, _ := http.NewRequest("GET", url, strings.NewReader("")) - - rq.Header.Set(model.HEADER_AUTH, model.HEADER_BEARER+" "+c.Session.Token) - - if rp, err := HttpClient.Do(rq); err != nil { - return nil, model.NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, model.AppErrorFromJson(rp.Body) - } else { - return rp, nil - } - } - - if result, err := doAllow(); err != nil { + if err != nil { c.Err = err return - } else { - //defer closeBody(result) - data := model.MapFromJson(result.Body) - redirectTo := data["redirect"] - http.Redirect(w, r, redirectTo, http.StatusFound) - return } + + http.Redirect(w, r, redirectUrl, http.StatusFound) + return } w.Header().Set("Content-Type", "text/html") @@ -418,14 +256,6 @@ func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - c.LogAudit("attempt") - r.ParseForm() code := r.FormValue("code") @@ -460,140 +290,21 @@ func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { return } - var oauthApp *model.OAuthApp - achan := app.Srv.Store.OAuth().GetApp(clientId) - if result := <-achan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - } - - if oauthApp.ClientSecret != secret { - c.LogAudit("fail - invalid client credentials") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "") - return - } - - var user *model.User - var accessData *model.AccessData - var accessRsp *model.AccessResponse - if grantType == model.ACCESS_TOKEN_GRANT_TYPE { - redirectUri := r.FormValue("redirect_uri") - authData := GetAuthData(code) - - if authData == nil { - c.LogAudit("fail - invalid auth code") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - if authData.IsExpired() { - <-app.Srv.Store.OAuth().RemoveAuthData(authData.Code) - c.LogAudit("fail - auth code expired") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - if authData.RedirectUri != redirectUri { - c.LogAudit("fail - redirect uri provided did not match previous redirect uri") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "") - return - } - - if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) { - c.LogAudit("fail - auth code is invalid") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - uchan := app.Srv.Store.User().Get(authData.UserId) - if result := <-uchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "") - return - } else { - user = result.Data.(*model.User) - } - - tchan := app.Srv.Store.OAuth().GetPreviousAccessData(user.Id, clientId) - if result := <-tchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal.app_error", nil, "") - return - } else if result.Data != nil { - accessData := result.Data.(*model.AccessData) - if accessData.IsExpired() { - if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { - c.Err = err - return - } else { - accessRsp = access - } - } else { - //return the same token and no need to create a new session - accessRsp = &model.AccessResponse{ - AccessToken: accessData.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - ExpiresIn: int32((accessData.ExpiresAt - model.GetMillis()) / 1000), - } - } - } else { - // create a new session and return new access token - var session *model.Session - if result, err := newSession(oauthApp.Name, user); err != nil { - c.Err = err - return - } else { - session = result - } - - accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt} - - if result := <-app.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "") - return - } - - accessRsp = &model.AccessResponse{ - AccessToken: session.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - RefreshToken: accessData.RefreshToken, - ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), - } - } - - <-app.Srv.Store.OAuth().RemoveAuthData(authData.Code) - } else { - // when grantType is refresh_token - if result := <-app.Srv.Store.OAuth().GetAccessDataByRefreshToken(refreshToken); result.Err != nil { - c.LogAudit("fail - refresh token is invalid") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "") - return - } else { - accessData = result.Data.(*model.AccessData) - } + redirectUri := r.FormValue("redirect_uri") - uchan := app.Srv.Store.User().Get(accessData.UserId) - if result := <-uchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "") - return - } else { - user = result.Data.(*model.User) - } + c.LogAudit("attempt") - if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { - c.Err = err - return - } else { - accessRsp = access - } + accessRsp, err := app.GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken) + if err != nil { + c.Err = err + return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") w.Header().Set("Pragma", "no-cache") - c.LogAuditWithUserId(user.Id, "success") + c.LogAudit("success") w.Write([]byte(accessRsp.ToJson())) } @@ -604,23 +315,13 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { loginHint := r.URL.Query().Get("login_hint") redirectTo := r.URL.Query().Get("redirect_to") - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return } - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_LOGIN - if len(teamId) != 0 { - stateProps["team_id"] = teamId - } - - if len(redirectTo) != 0 { - stateProps["redirect_to"] = redirectTo - } - - if authUrl, err := GetAuthorizationCode(c, service, stateProps, loginHint); err != nil { + if authUrl, err := app.GetOAuthLoginEndpoint(service, teamId, redirectTo, loginHint); err != nil { c.Err = err return } else { @@ -628,59 +329,22 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } } -func getTeamIdFromQuery(query url.Values) (string, *model.AppError) { - hash := query.Get("h") - inviteId := query.Get("id") - - if len(hash) > 0 { - data := query.Get("d") - props := model.MapFromJson(strings.NewReader(data)) - - if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { - return "", model.NewLocAppError("getTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "") - } - - t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours - return "", model.NewLocAppError("getTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "") - } - - return props["id"], nil - } else if len(inviteId) > 0 { - if result := <-app.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { - // soft fail, so we still create user but don't auto-join team - l4g.Error("%v", result.Err) - } else { - return result.Data.(*model.Team).Id, nil - } - } - - return "", nil -} - func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) service := params["service"] if !utils.Cfg.TeamSettings.EnableUserCreation { - c.Err = model.NewLocAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented + c.Err = model.NewAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) return } - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return } - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_SIGNUP - if len(teamId) != 0 { - stateProps["team_id"] = teamId - } - - if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil { + if authUrl, err := app.GetOAuthSignupEndpoint(service, teamId); err != nil { c.Err = err return } else { @@ -688,201 +352,36 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } } -func GetAuthorizationCode(c *Context, service string, props map[string]string, loginHint string) (string, *model.AppError) { - - sso := utils.Cfg.GetSSOService(service) - if sso != nil && !sso.Enable { - return "", model.NewLocAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service) - } - - clientId := sso.Id - endpoint := sso.AuthEndpoint - scope := sso.Scope - - props["hash"] = model.HashPassword(clientId) - state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) - - redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete" - - authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state) - - if len(scope) > 0 { - authUrl += "&scope=" + utils.UrlEncode(scope) - } - - if len(loginHint) > 0 { - authUrl += "&login_hint=" + utils.UrlEncode(loginHint) - } - - return authUrl, nil -} - -func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) { - sso := utils.Cfg.GetSSOService(service) - if sso == nil || !sso.Enable { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service) - } - - stateStr := "" - if b, err := b64.StdEncoding.DecodeString(state); err != nil { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error()) - } else { - stateStr = string(b) - } - - stateProps := model.MapFromJson(strings.NewReader(stateStr)) - - if !model.ComparePassword(stateProps["hash"], sso.Id) { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "") - } - - teamId := stateProps["team_id"] - - p := url.Values{} - p.Set("client_id", sso.Id) - p.Set("client_secret", sso.Secret) - p.Set("code", code) - p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - p.Set("redirect_uri", redirectUri) - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, - } - client := &http.Client{Transport: tr} - req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode())) - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") - - var ar *model.AccessResponse - var respBody []byte - if resp, err := client.Do(req); err != nil { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error()) - } else { - ar = model.AccessResponseFromJson(resp.Body) - defer func() { - ioutil.ReadAll(resp.Body) - resp.Body.Close() - }() - if ar == nil { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, "") - } - } - - if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+string(respBody)) - } - - if len(ar.AccessToken) == 0 { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "") - } - - p = url.Values{} - p.Set("access_token", ar.AccessToken) - req, _ = http.NewRequest("GET", sso.UserApiEndpoint, strings.NewReader("")) - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "Bearer "+ar.AccessToken) - - if resp, err := client.Do(req); err != nil { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", - map[string]interface{}{"Service": service}, err.Error()) - } else { - return resp.Body, teamId, stateProps, nil - } - -} - -func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, email string) { - authData := "" - ssoEmail := "" - provider := einterfaces.GetOauthProvider(service) - if provider == nil { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", - map[string]interface{}{"Service": strings.Title(service)}, "") - return - } else { - ssoUser := provider.GetUserFromJson(userData) - ssoEmail = ssoUser.Email - - if ssoUser.AuthData != nil { - authData = *ssoUser.AuthData - } - } - - if len(authData) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error", - map[string]interface{}{"Service": service}, "") - return - } - - if len(email) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "") - return - } - - var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { - c.Err = result.Err - return - } else { - user = result.Data.(*model.User) - } - - if err := app.RevokeAllSessions(user.Id); err != nil { - c.Err = err - return - } - c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") +func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) - if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil { - c.Err = result.Err + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("deleteOAuthApp", "id") return } - go func() { - if err := app.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, utils.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - } - }() -} - -func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } + c.LogAudit("attempt") if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("deleteOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } - c.LogAudit("attempt") - - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteOAuthApp", "id") + oauthApp, err := app.GetOAuthApp(id) + if err != nil { + c.Err = err return } - if result := <-app.Srv.Store.OAuth().GetApp(id); result.Err != nil { - c.Err = result.Err + if c.Session.UserId != oauthApp.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("deleteOAuthApp", "api.oauth.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusForbidden) return - } else { - if c.Session.UserId != result.Data.(*model.OAuthApp).CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.delete.permissions.app_error", nil, "user_id="+c.Session.UserId) - return - } } - if err := (<-app.Srv.Store.OAuth().DeleteApp(id)).Err; err != nil { + err = app.DeleteOAuthApp(id) + if err != nil { c.Err = err return } @@ -892,37 +391,11 @@ func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) id := params["id"] - // revoke app sessions - if result := <-app.Srv.Store.OAuth().GetAccessDataByUserForApp(c.Session.UserId, id); result.Err != nil { - c.Err = result.Err - return - } else { - accessData := result.Data.([]*model.AccessData) - - for _, a := range accessData { - if err := app.RevokeAccessToken(a.Token); err != nil { - c.Err = err - return - } - - if rad := <-app.Srv.Store.OAuth().RemoveAccessData(a.Token); rad.Err != nil { - c.Err = rad.Err - return - } - } - } - - // Deauthorize the app - if err := (<-app.Srv.Store.Preference().Delete(c.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, id)).Err; err != nil { + err := app.DeauthorizeOAuthAppForUser(c.Session.UserId, id) + if err != nil { c.Err = err return } @@ -932,78 +405,25 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } func regenerateOAuthSecret(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("registerOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) id := params["id"] - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(id); result.Err != nil { - c.Err = model.NewLocAppError("regenerateOAuthSecret", "api.oauth.allow_oauth.database.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - - if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.Err = model.NewLocAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - - oauthApp.ClientSecret = model.NewId() - if update := <-app.Srv.Store.OAuth().UpdateApp(oauthApp); update.Err != nil { - c.Err = update.Err - return - } - - w.Write([]byte(oauthApp.ToJson())) + oauthApp, err := app.GetOAuthApp(id) + if err != nil { + c.Err = err return } -} - -func newSession(appName string, user *model.User) (*model.Session, *model.AppError) { - // set new token an session - session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true} - session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays) - session.AddProp(model.SESSION_PROP_PLATFORM, appName) - session.AddProp(model.SESSION_PROP_OS, "OAuth2") - session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2") - - if result := <-app.Srv.Store.Session().Save(session); result.Err != nil { - return nil, model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_session.app_error", nil, "") - } else { - session = result.Data.(*model.Session) - app.AddSessionToCache(session) - } - - return session, nil -} - -func newSessionUpdateToken(appName string, accessData *model.AccessData, user *model.User) (*model.AccessResponse, *model.AppError) { - var session *model.Session - <-app.Srv.Store.Session().Remove(accessData.Token) //remove the previous session - if result, err := newSession(appName, user); err != nil { - return nil, err - } else { - session = result + if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { + c.Err = model.NewAppError("regenerateOAuthSecret", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) + return } - accessData.Token = session.Token - accessData.ExpiresAt = session.ExpiresAt - if result := <-app.Srv.Store.OAuth().UpdateAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) - return nil, model.NewLocAppError("getAccessToken", "web.get_access_token.internal_saving.app_error", nil, "") - } - accessRsp := &model.AccessResponse{ - AccessToken: session.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), + oauthApp, err = app.RegenerateOAuthAppSecret(oauthApp) + if err != nil { + c.Err = err + return } - return accessRsp, nil + w.Write([]byte(oauthApp.ToJson())) } diff --git a/api/oauth_test.go b/api/oauth_test.go index 5f55ae8f0..3dcaa0ddf 100644 --- a/api/oauth_test.go +++ b/api/oauth_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api @@ -491,7 +491,7 @@ func TestOAuthAuthorize(t *testing.T) { } authToken := Client.AuthType + " " + Client.AuthToken - if r, err := HttpGet(Client.Url+"/oauth/authorize?client_id="+oauthApp.Id+"&&redirect_uri=http://example.com&response_type="+model.AUTHCODE_RESPONSE_TYPE, Client.HttpClient, authToken, true); err != nil { + if r, err := HttpGet(Client.Url+"/oauth/authorize?client_id="+oauthApp.Id+"&redirect_uri=http://example.com&response_type="+model.AUTHCODE_RESPONSE_TYPE, Client.HttpClient, authToken, true); err != nil { t.Fatal(err) closeBody(r) } diff --git a/api/post.go b/api/post.go index bfc68a0d0..192b01bd5 100644 --- a/api/post.go +++ b/api/post.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/post_test.go b/api/post_test.go index d2297fb69..8dc164c1b 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/preference.go b/api/preference.go index 5384afbb5..9fdc0279c 100644 --- a/api/preference.go +++ b/api/preference.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/preference_test.go b/api/preference_test.go index 3e41c884f..8388138c1 100644 --- a/api/preference_test.go +++ b/api/preference_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/reaction.go b/api/reaction.go index 85b44b82d..a4992d61b 100644 --- a/api/reaction.go +++ b/api/reaction.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/reaction_test.go b/api/reaction_test.go index dad5a6a0c..27a296930 100644 --- a/api/reaction_test.go +++ b/api/reaction_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/server_test.go b/api/server_test.go index a9837203f..53ad652c9 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/status.go b/api/status.go index df4be4603..b945d7e90 100644 --- a/api/status.go +++ b/api/status.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/status_test.go b/api/status_test.go index f886d1044..7aa6a2299 100644 --- a/api/status_test.go +++ b/api/status_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/team.go b/api/team.go index 899948a94..c7fa61df6 100644 --- a/api/team.go +++ b/api/team.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/team_test.go b/api/team_test.go index 9dd598834..a7cffbb34 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/user.go b/api/user.go index 7f422b355..8b32dff36 100644 --- a/api/user.go +++ b/api/user.go @@ -1,13 +1,11 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api import ( - "bytes" b64 "encoding/base64" "fmt" - "io" "net/http" "strconv" "strings" @@ -132,52 +130,6 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(user.ToJson())) } -func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { - buf := bytes.Buffer{} - buf.ReadFrom(userData) - - authData := "" - provider := einterfaces.GetOauthProvider(service) - if provider == nil { - c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error", - map[string]interface{}{"Service": strings.Title(service)}, "") - return nil - } else { - authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes())) - } - - if len(authData) == 0 { - c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", - map[string]interface{}{"Service": service}, "") - return nil - } - - var user *model.User - var err *model.AppError - if user, err = app.GetUserByAuth(&authData, service); err != nil { - if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { - if user, err = app.CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), ""); err != nil { - c.Err = err - return nil - } - } - c.Err = err - return nil - } - - if err = app.UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil { - c.Err = err - return nil - } - - doLogin(c, w, r, user, "") - if c.Err != nil { - return nil - } - - return user -} - // User MUST be authenticated completely before calling Login func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { session, err := app.DoLogin(w, r, user, deviceId) @@ -914,41 +866,14 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { return } - c.LogAudit("attempt") - - var user *model.User - var err *model.AppError - if user, err = app.GetUserByEmail(email); err != nil { - c.LogAudit("fail - couldn't get user") - c.Err = err - return - } - - if err := app.CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil { - c.LogAuditWithUserId(user.Id, "failed - bad authentication") + link, err := app.SwitchEmailToOAuth(email, password, mfaToken, service) + if err != nil { c.Err = err return } - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO - stateProps["email"] = email - - m := map[string]string{} - if service == model.USER_AUTH_SERVICE_SAML { - m["follow_link"] = c.GetSiteURLHeader() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email - } else { - if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil { - c.LogAuditWithUserId(user.Id, "fail - oauth issue") - c.Err = err - return - } else { - m["follow_link"] = authUrl - } - } - - c.LogAuditWithUserId(user.Id, "success") - w.Write([]byte(model.MapToJson(m))) + c.LogAudit("success for email=" + email) + w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) } func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { @@ -966,51 +891,19 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - c.LogAudit("attempt") - - var user *model.User - var err *model.AppError - if user, err = app.GetUserByEmail(email); err != nil { - c.LogAudit("fail - couldn't get user") - c.Err = err - return - } - - if user.Id != c.Session.UserId { - c.LogAudit("fail - user ids didn't match") - c.Err = model.NewLocAppError("oauthToEmail", "api.user.oauth_to_email.context.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - - if err := app.UpdatePassword(user, password); err != nil { - c.LogAudit("fail - database issue") - c.Err = err - return - } - - go func() { - if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - } - }() - - if err := app.RevokeAllSessions(c.Session.UserId); err != nil { + link, err := app.SwitchOAuthToEmail(email, password, c.Session.UserId) + if err != nil { c.Err = err return } - c.LogAuditWithUserId(c.Session.UserId, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } - m := map[string]string{} - m["follow_link"] = "/login?extra=signin_change" - c.LogAudit("success") - w.Write([]byte(model.MapToJson(m))) + w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) } func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1044,55 +937,19 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var user *model.User - var err *model.AppError - if user, err = app.GetUserByEmail(email); err != nil { - c.LogAudit("fail - couldn't get user") - c.Err = err - return - } - - if err := app.CheckPasswordAndAllCriteria(user, emailPassword, token); err != nil { - c.LogAuditWithUserId(user.Id, "failed - bad authentication") - c.Err = err - return - } - - if err := app.RevokeAllSessions(user.Id); err != nil { + link, err := app.SwitchEmailToLdap(email, emailPassword, token, ldapId, ldapPassword) + if err != nil { c.Err = err return } - c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } - ldapInterface := einterfaces.GetLdapInterface() - if ldapInterface == nil { - c.Err = model.NewLocAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil { - c.LogAuditWithUserId(user.Id, "fail - ldap switch failed") - c.Err = err - return - } - - go func() { - if err := app.SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, utils.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - } - }() - - m := map[string]string{} - m["follow_link"] = "/login?extra=signin_change" - c.LogAudit("success") - w.Write([]byte(model.MapToJson(m))) + w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) } func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1120,66 +977,19 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var user *model.User - var err *model.AppError - if user, err = app.GetUserByEmail(email); err != nil { - c.LogAudit("fail - couldn't get user") - c.Err = err - return - } - - if user.AuthService != model.USER_AUTH_SERVICE_LDAP { - c.Err = model.NewLocAppError("ldapToEmail", "api.user.ldap_to_email.not_ldap_account.app_error", nil, "") - return - } - - ldapInterface := einterfaces.GetLdapInterface() - if ldapInterface == nil || user.AuthData == nil { - c.Err = model.NewLocAppError("ldapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - if err := ldapInterface.CheckPassword(*user.AuthData, ldapPassword); err != nil { - c.LogAuditWithUserId(user.Id, "fail - ldap authentication failed") - c.Err = err - return - } - - if err := app.CheckUserMfa(user, token); err != nil { - c.LogAuditWithUserId(user.Id, "fail - mfa token failed") - c.Err = err - return - } - - if err := app.UpdatePassword(user, emailPassword); err != nil { - c.LogAudit("fail - database issue") - c.Err = err - return - } - - if err := app.RevokeAllSessions(user.Id); err != nil { + link, err := app.SwitchLdapToEmail(ldapPassword, token, email, emailPassword) + if err != nil { c.Err = err return } - c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } - go func() { - if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - } - }() - - m := map[string]string{} - m["follow_link"] = "/login?extra=signin_change" - c.LogAudit("success") - w.Write([]byte(model.MapToJson(m))) + w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) } func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1330,7 +1140,7 @@ func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return diff --git a/api/user_test.go b/api/user_test.go index cdbccc57e..ff2101c17 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/webhook.go b/api/webhook.go index 12751943e..c17b5bc56 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/webhook_test.go b/api/webhook_test.go index 8a170bd9a..5a0e44630 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/webrtc.go b/api/webrtc.go index 7ba9d3762..58e35f2fa 100644 --- a/api/webrtc.go +++ b/api/webrtc.go @@ -1,21 +1,13 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api import ( - "crypto/hmac" - "crypto/sha1" - "crypto/tls" - "encoding/base64" "net/http" - "strconv" - "strings" - "time" l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" ) @@ -26,68 +18,12 @@ func InitWebrtc() { } func webrtcToken(c *Context, w http.ResponseWriter, r *http.Request) { - if token, err := getWebrtcToken(c.Session.Id); err != nil { + result, err := app.GetWebrtcInfoForSession(c.Session.Id) + + if err != nil { c.Err = err return - } else { - result := make(map[string]string) - result["token"] = token - result["gateway_url"] = *utils.Cfg.WebrtcSettings.GatewayWebsocketUrl - - if len(*utils.Cfg.WebrtcSettings.StunURI) > 0 { - result["stun_uri"] = *utils.Cfg.WebrtcSettings.StunURI - } - - if len(*utils.Cfg.WebrtcSettings.TurnURI) > 0 { - timestamp := strconv.FormatInt(utils.EndOfDay(time.Now().AddDate(0, 0, 1)).Unix(), 10) - username := timestamp + ":" + *utils.Cfg.WebrtcSettings.TurnUsername - - result["turn_uri"] = *utils.Cfg.WebrtcSettings.TurnURI - result["turn_password"] = generateTurnPassword(username, *utils.Cfg.WebrtcSettings.TurnSharedKey) - result["turn_username"] = username - } - w.Write([]byte(model.MapToJson(result))) } -} - -func getWebrtcToken(sessionId string) (string, *model.AppError) { - if !*utils.Cfg.WebrtcSettings.Enable { - return "", model.NewLocAppError("WebRTC.getWebrtcToken", "api.webrtc.disabled.app_error", nil, "") - } - - token := base64.StdEncoding.EncodeToString([]byte(sessionId)) - - data := make(map[string]string) - data["janus"] = "add_token" - data["token"] = token - data["transaction"] = model.NewId() - data["admin_secret"] = *utils.Cfg.WebrtcSettings.GatewayAdminSecret - - rq, _ := http.NewRequest("POST", *utils.Cfg.WebrtcSettings.GatewayAdminUrl, strings.NewReader(model.MapToJson(data))) - rq.Header.Set("Content-Type", "application/json") - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, - } - httpClient := &http.Client{Transport: tr} - if rp, err := httpClient.Do(rq); err != nil { - return "", model.NewLocAppError("WebRTC.Token", "model.client.connecting.app_error", nil, err.Error()) - } else if rp.StatusCode >= 300 { - defer app.CloseBody(rp) - return "", model.AppErrorFromJson(rp.Body) - } else { - janusResponse := model.GatewayResponseFromJson(rp.Body) - if janusResponse.Status != "success" { - return "", model.NewLocAppError("getWebrtcToken", "api.webrtc.register_token.app_error", nil, "") - } - } - - return token, nil -} -func generateTurnPassword(username string, secret string) string { - key := []byte(secret) - h := hmac.New(sha1.New, key) - h.Write([]byte(username)) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) + w.Write([]byte(result.ToJson())) } diff --git a/api/websocket.go b/api/websocket.go index 192513bc0..6f53023db 100644 --- a/api/websocket.go +++ b/api/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/websocket_test.go b/api/websocket_test.go index d3d8fc4b2..bda014f06 100644 --- a/api/websocket_test.go +++ b/api/websocket_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api |