From cef5028cbeed93b6493f6d1f379afe4ca85535c8 Mon Sep 17 00:00:00 2001 From: Ruzette Tanyag Date: Tue, 28 Feb 2017 04:14:16 -0500 Subject: Implemented preferences endpoints for apiv4 (#5531) * implemented preferences endpoints for apiv4 * added user id in preferences endpoints --- api4/api.go | 3 +- api4/context.go | 24 ++++ api4/params.go | 38 ++++--- api4/preference.go | 153 ++++++++++++++++++++++++++ api4/preference_test.go | 287 ++++++++++++++++++++++++++++++++++++++++++++++++ app/preference.go | 44 ++++++++ model/client4.go | 60 ++++++++++ 7 files changed, 594 insertions(+), 15 deletions(-) create mode 100644 api4/preference.go create mode 100644 api4/preference_test.go diff --git a/api4/api.go b/api4/api.go index ca43e7275..c8c0e170b 100644 --- a/api4/api.go +++ b/api4/api.go @@ -130,7 +130,7 @@ func InitApi(full bool) { BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter() BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter() BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter() - BaseRoutes.Preferences = BaseRoutes.ApiRoot.PathPrefix("/preferences").Subrouter() + BaseRoutes.Preferences = BaseRoutes.User.PathPrefix("/preferences").Subrouter() BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter() BaseRoutes.Public = BaseRoutes.ApiRoot.PathPrefix("/public").Subrouter() @@ -146,6 +146,7 @@ func InitApi(full bool) { InitFile() InitSystem() InitWebhook() + InitPreference() app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/context.go b/api4/context.go index f0d8b0c5c..c30a975f2 100644 --- a/api4/context.go +++ b/api4/context.go @@ -431,3 +431,27 @@ func (c *Context) RequireEmail() *Context { return c } + +func (c *Context) RequireCategory() *Context { + if c.Err != nil { + return c + } + + if !model.IsValidAlphaNum(c.Params.Category, true) { + c.SetInvalidUrlParam("category") + } + + return c +} + +func (c *Context) RequirePreferenceName() *Context { + if c.Err != nil { + return c + } + + if !model.IsValidAlphaNum(c.Params.PreferenceName, true) { + c.SetInvalidUrlParam("preference_name") + } + + return c +} diff --git a/api4/params.go b/api4/params.go index 9b371e71a..b1688a859 100644 --- a/api4/params.go +++ b/api4/params.go @@ -17,20 +17,22 @@ const ( ) type ApiParams struct { - UserId string - TeamId string - ChannelId string - PostId string - FileId string - CommandId string - HookId string - EmojiId string - Email string - Username string - TeamName string - ChannelName string - Page int - PerPage int + UserId string + TeamId string + ChannelId string + PostId string + FileId string + CommandId string + HookId string + EmojiId string + Email string + Username string + TeamName string + ChannelName string + PreferenceName string + Category string + Page int + PerPage int } func ApiParamsFromRequest(r *http.Request) *ApiParams { @@ -86,6 +88,14 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams { params.ChannelName = val } + if val, ok := props["category"]; ok { + params.Category = val + } + + if val, ok := props["preference_name"]; ok { + params.PreferenceName = val + } + if val, err := strconv.Atoi(r.URL.Query().Get("page")); err != nil { params.Page = PAGE_DEFAULT } else { diff --git a/api4/preference.go b/api4/preference.go new file mode 100644 index 000000000..9ba6b85d2 --- /dev/null +++ b/api4/preference.go @@ -0,0 +1,153 @@ +// // Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// // See License.txt for license information. + +package api4 + +import ( + "net/http" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/app" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +func InitPreference() { + l4g.Debug(utils.T("api.preference.init.debug")) + + BaseRoutes.Preferences.Handle("", ApiSessionRequired(getPreferences)).Methods("GET") + BaseRoutes.Preferences.Handle("", ApiSessionRequired(updatePreferences)).Methods("PUT") + BaseRoutes.Preferences.Handle("/delete", ApiSessionRequired(deletePreferences)).Methods("POST") + BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}", ApiSessionRequired(getPreferencesByCategory)).Methods("GET") + BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}/name/{preference_name:[A-Za-z0-9_]+}", ApiSessionRequired(getPreferenceByCategoryAndName)).Methods("GET") +} + +func getPreferences(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + if preferences, err := app.GetPreferencesForUser(c.Params.UserId); err != nil { + c.Err = err + return + } else { + w.Write([]byte(preferences.ToJson())) + return + } +} + +func getPreferencesByCategory(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId().RequireCategory() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + if preferences, err := app.GetPreferenceByCategoryForUser(c.Params.UserId, c.Params.Category); err != nil { + c.Err = err + return + } else { + w.Write([]byte(preferences.ToJson())) + return + } +} + +func getPreferenceByCategoryAndName(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId().RequireCategory().RequirePreferenceName() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + if preferences, err := app.GetPreferenceByCategoryAndNameForUser(c.Params.UserId, c.Params.Category, c.Params.PreferenceName); err != nil { + c.Err = err + return + } else { + w.Write([]byte(preferences.ToJson())) + return + } +} + +func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + preferences, err := model.PreferencesFromJson(r.Body) + if err != nil { + c.SetInvalidParam("preferences") + return + } + + for _, preference := range preferences { + if c.Params.UserId != preference.UserId { + c.Err = model.NewAppError("savePreferences", "api.preference.update_preferences.set.app_error", nil, + c.T("api.preference.update_preferences.set_details.app_error", + map[string]interface{}{"SessionUserId": c.Params.UserId, "PreferenceUserId": preference.UserId}), + http.StatusForbidden) + return + } + } + + if _, err := app.UpdatePreferences(preferences); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} + +func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + preferences, err := model.PreferencesFromJson(r.Body) + if err != nil { + c.SetInvalidParam("preferences") + return + } + + for _, preference := range preferences { + if c.Params.UserId != preference.UserId { + c.Err = model.NewAppError("deletePreferences", "api.preference.delete_preferences.delete.app_error", nil, + c.T("api.preference.delete_preferences.delete.app_error", + map[string]interface{}{"SessionUserId": c.Params.UserId, "PreferenceUserId": preference.UserId}), + http.StatusForbidden) + return + } + } + + if _, err := app.DeletePreferences(c.Params.UserId, preferences); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/preference_test.go b/api4/preference_test.go new file mode 100644 index 000000000..02df99b9b --- /dev/null +++ b/api4/preference_test.go @@ -0,0 +1,287 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "github.com/mattermost/platform/model" + "testing" +) + +func TestGetPreferences(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + category := model.NewId() + preferences1 := model.Preferences{ + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: model.NewId(), + Name: model.NewId(), + }, + } + + Client.UpdatePreferences(user1.Id, &preferences1) + + prefs, resp := Client.GetPreferences(user1.Id) + CheckNoError(t, resp) + if len(prefs) != 4 { + t.Fatal("received the wrong number of preferences") + } + + for _, preference := range prefs { + if preference.UserId != th.BasicUser.Id { + t.Fatal("user id does not match") + } + } + + th.LoginBasic2() + + prefs, resp = Client.GetPreferences(th.BasicUser2.Id) + CheckNoError(t, resp) + + if len(prefs) == 0 { + t.Fatal("received the wrong number of preferences") + } + + _, resp = Client.GetPreferences(th.BasicUser.Id) + CheckForbiddenStatus(t, resp) + + Client.Logout() + _, resp = Client.GetPreferences(th.BasicUser2.Id) + CheckUnauthorizedStatus(t, resp) +} + +func TestGetPreferencesByCategory(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + category := model.NewId() + preferences1 := model.Preferences{ + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: model.NewId(), + Name: model.NewId(), + }, + } + + Client.UpdatePreferences(user1.Id, &preferences1) + + prefs, resp := Client.GetPreferencesByCategory(user1.Id, category) + CheckNoError(t, resp) + + if len(prefs) != 2 { + t.Fatalf("received the wrong number of preferences %v:%v", len(prefs), 2) + } + + prefs, resp = Client.GetPreferencesByCategory(user1.Id, "junk") + CheckNotFoundStatus(t, resp) + + th.LoginBasic2() + + prefs, resp = Client.GetPreferencesByCategory(th.BasicUser2.Id, category) + CheckNotFoundStatus(t, resp) + + prefs, resp = Client.GetPreferencesByCategory(user1.Id, category) + CheckForbiddenStatus(t, resp) + + prefs, resp = Client.GetPreferencesByCategory(th.BasicUser2.Id, "junk") + CheckNotFoundStatus(t, resp) + + if len(prefs) != 0 { + t.Fatal("received the wrong number of preferences") + } + + Client.Logout() + _, resp = Client.GetPreferencesByCategory(th.BasicUser2.Id, category) + CheckUnauthorizedStatus(t, resp) +} + +func TestGetPreferenceByCategoryAndName(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + th.LoginBasic() + user := th.BasicUser + name := model.NewId() + value := model.NewId() + + preferences := model.Preferences{ + { + UserId: user.Id, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: name, + Value: value, + }, + { + UserId: user.Id, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: model.NewId(), + Value: model.NewId(), + }, + } + + Client.UpdatePreferences(user.Id, &preferences) + + pref, resp := Client.GetPreferenceByCategoryAndName(user.Id, model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, name) + CheckNoError(t, resp) + + if (pref.UserId != preferences[0].UserId) && (pref.Category != preferences[0].Category) && (pref.Name != preferences[0].Name) { + t.Fatal("preference saved incorrectly") + } + + preferences[0].Value = model.NewId() + Client.UpdatePreferences(user.Id, &preferences) + + _, resp = Client.GetPreferenceByCategoryAndName(user.Id, "junk", preferences[0].Name) + CheckBadRequestStatus(t, resp) + + _, resp = Client.GetPreferenceByCategoryAndName(user.Id, preferences[0].Category, "junk") + CheckBadRequestStatus(t, resp) + + _, resp = Client.GetPreferenceByCategoryAndName(th.BasicUser2.Id, preferences[0].Category, "junk") + CheckForbiddenStatus(t, resp) + + _, resp = Client.GetPreferenceByCategoryAndName(user.Id, preferences[0].Category, preferences[0].Name) + CheckNoError(t, resp) + + Client.Logout() + _, resp = Client.GetPreferenceByCategoryAndName(user.Id, preferences[0].Category, preferences[0].Name) + CheckUnauthorizedStatus(t, resp) + +} + +func TestUpdatePreferences(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + category := model.NewId() + preferences1 := model.Preferences{ + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: category, + Name: model.NewId(), + }, + { + UserId: user1.Id, + Category: model.NewId(), + Name: model.NewId(), + }, + } + + _, resp := Client.UpdatePreferences(user1.Id, &preferences1) + CheckNoError(t, resp) + + preferences := model.Preferences{ + { + UserId: model.NewId(), + Category: category, + Name: model.NewId(), + }, + } + + _, resp = Client.UpdatePreferences(user1.Id, &preferences) + CheckForbiddenStatus(t, resp) + + preferences = model.Preferences{ + { + UserId: user1.Id, + Name: model.NewId(), + }, + } + + _, resp = Client.UpdatePreferences(user1.Id, &preferences) + CheckBadRequestStatus(t, resp) + + _, resp = Client.UpdatePreferences(th.BasicUser2.Id, &preferences) + CheckForbiddenStatus(t, resp) + + Client.Logout() + _, resp = Client.UpdatePreferences(user1.Id, &preferences1) + CheckUnauthorizedStatus(t, resp) +} + +func TestDeletePreferences(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + th.LoginBasic() + + prefs, resp := Client.GetPreferences(th.BasicUser.Id) + originalCount := len(prefs) + + // save 10 preferences + var preferences model.Preferences + for i := 0; i < 10; i++ { + preference := model.Preference{ + UserId: th.BasicUser.Id, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: model.NewId(), + } + preferences = append(preferences, preference) + } + + Client.UpdatePreferences(th.BasicUser.Id, &preferences) + + // delete 10 preferences + th.LoginBasic2() + + _, resp = Client.DeletePreferences(th.BasicUser2.Id, &preferences) + CheckForbiddenStatus(t, resp) + + th.LoginBasic() + + _, resp = Client.DeletePreferences(th.BasicUser.Id, &preferences) + CheckNoError(t, resp) + + _, resp = Client.DeletePreferences(th.BasicUser2.Id, &preferences) + CheckForbiddenStatus(t, resp) + + prefs, resp = Client.GetPreferences(th.BasicUser.Id) + if len(prefs) != originalCount { + t.Fatal("should've deleted preferences") + } + + Client.Logout() + _, resp = Client.DeletePreferences(th.BasicUser.Id, &preferences) + CheckUnauthorizedStatus(t, resp) +} diff --git a/app/preference.go b/app/preference.go index 4e492c4a8..ff251fb16 100644 --- a/app/preference.go +++ b/app/preference.go @@ -5,12 +5,56 @@ package app import ( "github.com/mattermost/platform/model" + "net/http" ) func GetPreferencesForUser(userId string) (model.Preferences, *model.AppError) { if result := <-Srv.Store.Preference().GetAll(userId); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest return nil, result.Err } else { return result.Data.(model.Preferences), nil } } + +func GetPreferenceByCategoryForUser(userId string, category string) (model.Preferences, *model.AppError) { + if result := <-Srv.Store.Preference().GetCategory(userId, category); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err + } else if len(result.Data.(model.Preferences)) == 0 { + err := model.NewAppError("getPreferenceCategory", "api.preference.preferences_category.get.app_error", nil, "", http.StatusNotFound) + return nil, err + } else { + return result.Data.(model.Preferences), nil + } +} + +func GetPreferenceByCategoryAndNameForUser(userId string, category string, preferenceName string) (*model.Preference, *model.AppError) { + if result := <-Srv.Store.Preference().Get(userId, category, preferenceName); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err + } else { + data := result.Data.(model.Preference) + return &data, nil + } +} + +func UpdatePreferences(preferences model.Preferences) (bool, *model.AppError) { + if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return false, result.Err + } + + return true, nil +} + +func DeletePreferences(userId string, preferences model.Preferences) (bool, *model.AppError) { + for _, preference := range preferences { + if result := <-Srv.Store.Preference().Delete(userId, preference.Category, preference.Name); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return false, result.Err + } + } + + return true, nil +} diff --git a/model/client4.go b/model/client4.go index ff35f3cf3..cbaf8234b 100644 --- a/model/client4.go +++ b/model/client4.go @@ -141,6 +141,10 @@ func (c *Client4) GetIncomingWebhooksRoute() string { return fmt.Sprintf("/hooks/incoming") } +func (c *Client4) GetPreferencesRoute(userId string) string { + return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences") +} + func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { return c.DoApiRequest(http.MethodGet, url, "", etag) } @@ -871,3 +875,59 @@ func (c *Client4) GetIncomingWebhooksForTeam(teamId string, page int, perPage in return IncomingWebhookListFromJson(r.Body), BuildResponse(r) } } + +// Preferences Section + +// GetPreferences returns the user's preferences +func (c *Client4) GetPreferences(userId string) (Preferences, *Response) { + if r, err := c.DoApiGet(c.GetPreferencesRoute(userId), ""); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + preferences, _ := PreferencesFromJson(r.Body) + defer closeBody(r) + return preferences, BuildResponse(r) + } +} + +// UpdatePreferences saves the user's preferences +func (c *Client4) UpdatePreferences(userId string, preferences *Preferences) (bool, *Response) { + if r, err := c.DoApiPut(c.GetPreferencesRoute(userId), preferences.ToJson()); err != nil { + return false, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return true, BuildResponse(r) + } +} + +// DeletePreferences deletes the user's preferences +func (c *Client4) DeletePreferences(userId string, preferences *Preferences) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPreferencesRoute(userId)+"/delete", preferences.ToJson()); err != nil { + return false, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return true, BuildResponse(r) + } +} + +// GetPreferencesByCategory returns the user's preferences from the provided category string +func (c *Client4) GetPreferencesByCategory(userId string, category string) (Preferences, *Response) { + url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s", category) + if r, err := c.DoApiGet(url, ""); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + preferences, _ := PreferencesFromJson(r.Body) + defer closeBody(r) + return preferences, BuildResponse(r) + } +} + +// GetPreferenceByCategoryAndName returns the user's preferences from the provided category and preference name string +func (c *Client4) GetPreferenceByCategoryAndName(userId string, category string, preferenceName string) (*Preference, *Response) { + url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s/name/%v", category, preferenceName) + if r, err := c.DoApiGet(url, ""); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return PreferenceFromJson(r.Body), BuildResponse(r) + } +} -- cgit v1.2.3-1-g7c22