From ad0ed008fe54534fcc089f479df606ab921901a9 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 14 Mar 2017 09:35:48 -0400 Subject: Implement brand image endpoints for APIv4 (#5733) * Implement brand image endpoints for APIv4 * Fix unit test --- api/admin.go | 6 ----- api4/api.go | 4 ++++ api4/brand.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ api4/brand_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ app/brand.go | 16 ++++++------- i18n/en.json | 4 ++++ model/client4.go | 50 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 api4/brand.go create mode 100644 api4/brand_test.go diff --git a/api/admin.go b/api/admin.go index 5e0d8c28c..14c355701 100644 --- a/api/admin.go +++ b/api/admin.go @@ -230,12 +230,6 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { } func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { - if len(utils.Cfg.FileSettings.DriverName) == 0 { - c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.storage.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if r.ContentLength > *utils.Cfg.FileSettings.MaxFileSize { c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.too_large.app_error", nil, "") c.Err.StatusCode = http.StatusRequestEntityTooLarge diff --git a/api4/api.go b/api4/api.go index 53d7394c7..422af7b7b 100644 --- a/api4/api.go +++ b/api4/api.go @@ -69,6 +69,8 @@ type Routes struct { LDAP *mux.Router // 'api/v4/ldap' + Brand *mux.Router // 'api/v4/brand' + System *mux.Router // 'api/v4/system' Preferences *mux.Router // 'api/v4/preferences' @@ -142,6 +144,7 @@ func InitApi(full bool) { BaseRoutes.Compliance = BaseRoutes.ApiRoot.PathPrefix("/compliance").Subrouter() BaseRoutes.Cluster = BaseRoutes.ApiRoot.PathPrefix("/cluster").Subrouter() BaseRoutes.LDAP = BaseRoutes.ApiRoot.PathPrefix("/ldap").Subrouter() + BaseRoutes.Brand = BaseRoutes.ApiRoot.PathPrefix("/brand").Subrouter() BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter() BaseRoutes.Preferences = BaseRoutes.User.PathPrefix("/preferences").Subrouter() BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter() @@ -164,6 +167,7 @@ func InitApi(full bool) { InitCompliance() InitCluster() InitLdap() + InitBrand() app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/brand.go b/api4/brand.go new file mode 100644 index 000000000..00e6bbbff --- /dev/null +++ b/api4/brand.go @@ -0,0 +1,70 @@ +// 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 InitBrand() { + l4g.Debug(utils.T("api.brand.init.debug")) + + BaseRoutes.Brand.Handle("/image", ApiHandlerTrustRequester(getBrandImage)).Methods("GET") + BaseRoutes.Brand.Handle("/image", ApiSessionRequired(uploadBrandImage)).Methods("POST") +} + +func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { + // No permission check required + + if img, err := app.GetBrandImage(); err != nil { + w.Write(nil) + } else { + w.Header().Set("Content-Type", "image/png") + w.Write(img) + } +} + +func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { + if r.ContentLength > *utils.Cfg.FileSettings.MaxFileSize { + c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge) + return + } + + if err := r.ParseMultipartForm(*utils.Cfg.FileSettings.MaxFileSize); err != nil { + c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.parse.app_error", nil, "", http.StatusBadRequest) + return + } + + m := r.MultipartForm + + imageArray, ok := m.File["image"] + if !ok { + c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.no_file.app_error", nil, "", http.StatusBadRequest) + return + } + + if len(imageArray) <= 0 { + c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.array.app_error", nil, "", http.StatusBadRequest) + return + } + + if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if err := app.SaveBrandImage(imageArray[0]); err != nil { + c.Err = err + return + } + + c.LogAudit("") + + ReturnStatusOK(w) +} diff --git a/api4/brand_test.go b/api4/brand_test.go new file mode 100644 index 000000000..fd5e472a8 --- /dev/null +++ b/api4/brand_test.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + "testing" +) + +func TestGetBrandImage(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + data, resp := Client.GetBrandImage() + CheckNoError(t, resp) + + if len(data) != 0 { + t.Fatal("no image uploaded - should be empty") + } + + Client.Logout() + data, resp = Client.GetBrandImage() + CheckNoError(t, resp) + + if len(data) != 0 { + t.Fatal("no image uploaded - should be empty") + } + + data, resp = th.SystemAdminClient.GetBrandImage() + CheckNoError(t, resp) + + if len(data) != 0 { + t.Fatal("no image uploaded - should be empty") + } +} + +func TestUploadBrandImage(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + data, err := readTestFile("test.png") + if err != nil { + t.Fatal(err) + } + + ok, resp := Client.UploadBrandImage(data) + CheckForbiddenStatus(t, resp) + if ok { + t.Fatal("Should return false, set brand image not allowed") + } + + // status code returns either forbidden or unauthorized + // note: forbidden is set as default at Client4.SetProfileImage when request is terminated early by server + Client.Logout() + _, resp = Client.UploadBrandImage(data) + if resp.StatusCode == http.StatusForbidden { + CheckForbiddenStatus(t, resp) + } else if resp.StatusCode == http.StatusUnauthorized { + CheckUnauthorizedStatus(t, resp) + } else { + t.Fatal("Should have failed either forbidden or unauthorized") + } + + _, resp = th.SystemAdminClient.UploadBrandImage(data) + CheckNotImplementedStatus(t, resp) +} diff --git a/app/brand.go b/app/brand.go index aeecc6972..9b3df3145 100644 --- a/app/brand.go +++ b/app/brand.go @@ -13,11 +13,13 @@ import ( ) func SaveBrandImage(imageData *multipart.FileHeader) *model.AppError { + if len(utils.Cfg.FileSettings.DriverName) == 0 { + return model.NewAppError("SaveBrandImage", "api.admin.upload_brand_image.storage.app_error", nil, "", http.StatusNotImplemented) + } + brandInterface := einterfaces.GetBrandInterface() if brandInterface == nil { - err := model.NewLocAppError("SaveBrandImage", "api.admin.upload_brand_image.not_available.app_error", nil, "") - err.StatusCode = http.StatusNotImplemented - return err + return model.NewAppError("SaveBrandImage", "api.admin.upload_brand_image.not_available.app_error", nil, "", http.StatusNotImplemented) } if err := brandInterface.SaveBrandImage(imageData); err != nil { @@ -29,16 +31,12 @@ func SaveBrandImage(imageData *multipart.FileHeader) *model.AppError { func GetBrandImage() ([]byte, *model.AppError) { if len(utils.Cfg.FileSettings.DriverName) == 0 { - err := model.NewLocAppError("GetBrandImage", "api.admin.get_brand_image.storage.app_error", nil, "") - err.StatusCode = http.StatusNotImplemented - return nil, err + return nil, model.NewAppError("GetBrandImage", "api.admin.get_brand_image.storage.app_error", nil, "", http.StatusNotImplemented) } brandInterface := einterfaces.GetBrandInterface() if brandInterface == nil { - err := model.NewLocAppError("GetBrandImage", "api.admin.get_brand_image.not_available.app_error", nil, "") - err.StatusCode = http.StatusNotImplemented - return nil, err + return nil, model.NewAppError("GetBrandImage", "api.admin.get_brand_image.not_available.app_error", nil, "", http.StatusNotImplemented) } if img, err := brandInterface.GetBrandImage(); err != nil { diff --git a/i18n/en.json b/i18n/en.json index 83a59d9e1..8e63fe567 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -151,6 +151,10 @@ "id": "api.admin.upload_brand_image.too_large.app_error", "translation": "Unable to upload file. File is too large." }, + { + "id": "api.brand.init.debug", + "translation": "Initializing brand API routes" + }, { "id": "api.api.init.parsing_templates.debug", "translation": "Parsing server templates at %v" diff --git a/model/client4.go b/model/client4.go index 799fec802..df09174d5 100644 --- a/model/client4.go +++ b/model/client4.go @@ -190,6 +190,10 @@ func (c *Client4) GetLdapRoute() string { return fmt.Sprintf("/ldap") } +func (c *Client4) GetBrandRoute() string { + return fmt.Sprintf("/brand") +} + func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { return c.DoApiRequest(http.MethodGet, url, "", etag) } @@ -1498,3 +1502,49 @@ func (c *Client4) TestLdap() (bool, *Response) { return CheckStatusOK(r), BuildResponse(r) } } + +// Brand Section + +// GetBrandImage retrieves the previously uploaded brand image. +func (c *Client4) GetBrandImage() ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetBrandRoute()+"/image", ""); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: NewAppError("GetBrandImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)} + } else { + return data, BuildResponse(r) + } +} + +// UploadBrandImage sets the brand image for the system. +func (c *Client4) UploadBrandImage(data []byte) (bool, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("image", "brand.png"); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetBrandRoute()+"/image", bytes.NewReader(body.Bytes())) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + rq.Close = true + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil { + return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetBrandRoute()+"/image", "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} + } else if rp.StatusCode >= 300 { + return false, &Response{StatusCode: rp.StatusCode, Error: AppErrorFromJson(rp.Body)} + } else { + defer closeBody(rp) + return CheckStatusOK(rp), BuildResponse(rp) + } +} -- cgit v1.2.3-1-g7c22