1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-07-03 23:30:29 +02:00

Refactor error usage from the store level up and add API helpers (#3792)

* Refactor error usage from the store level up and add API helpers

* Complete API tests

* Fix merge errorResponse calls

* Remove ensure helpers to allow for custom messages on permission errors

* Fix bad import and call

* Remove bad user check on auth that was added as part of the main merge

* Fix empty list test

* Replace deprecated proxy calls to ioutil.ReadAll with io.ReadAll

* Add information to the NotFound errors

* Add context to all remaining errors and address review comments

* Fix linter

* Adapt the new card API endpoints to the error refactor

* Remove almost all customErrorResponse calls

* Add request entity too large to errorResponse and remove customErrorResponse

* Fix linter
This commit is contained in:
Miguel de la Cruz
2022-09-13 12:18:40 +02:00
committed by GitHub
parent ed655ac996
commit 08c0b7a2fd
68 changed files with 1349 additions and 922 deletions

View File

@ -4,7 +4,7 @@
package boards
import (
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"testing"
@ -113,7 +113,7 @@ func TestServeHTTP(t *testing.T) {
result := w.Result()
assert.NotNil(result)
defer result.Body.Close()
bodyBytes, err := ioutil.ReadAll(result.Body)
bodyBytes, err := io.ReadAll(result.Body)
assert.Nil(err)
bodyString := string(bodyBytes)

View File

@ -2,11 +2,12 @@ package api
import (
"encoding/json"
"io/ioutil"
"io"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
@ -20,16 +21,16 @@ func (a *API) handleAdminSetPassword(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
username := vars["username"]
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var requestData AdminSetPasswordData
err = json.Unmarshal(requestBody, &requestData)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -38,13 +39,13 @@ func (a *API) handleAdminSetPassword(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("username", username)
if !strings.Contains(requestData.Password, "") {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "password is required", err)
a.errorResponse(w, r, model.NewErrBadRequest("password is required"))
return
}
err = a.app.UpdateUserPassword(username, requestData.Password)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -21,22 +21,14 @@ const (
HeaderRequestedWithXML = "XMLHttpRequest"
UploadFormFileKey = "file"
True = "true"
)
const (
ErrorNoTeamCode = 1000
ErrorNoTeamMessage = "No team"
)
var errAPINotSupportedInStandaloneMode = errors.New("API not supported in standalone mode")
type PermissionError struct {
msg string
}
func (pe PermissionError) Error() string {
return pe.msg
}
var (
ErrHandlerPanic = errors.New("http handler panic")
)
// ----------------------------------------------------------------------------------------------------
// REST APIs
@ -133,7 +125,7 @@ func (a *API) panicHandler(next http.Handler) http.Handler {
mlog.String("stack", string(debug.Stack())),
mlog.String("uri", r.URL.Path),
)
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", nil)
a.errorResponse(w, r, ErrHandlerPanic)
}
}()
@ -145,7 +137,7 @@ func (a *API) requireCSRFToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !a.checkCSRFToken(r) {
a.logger.Error("checkCSRFToken FAILED")
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "checkCSRFToken FAILED", nil)
a.errorResponse(w, r, model.NewErrBadRequest("checkCSRFToken FAILED"))
return
}
@ -184,34 +176,39 @@ func (a *API) userIsGuest(userID string) (bool, error) {
// Response helpers
func (a *API) errorResponse(w http.ResponseWriter, api string, code int, message string, sourceError error) {
if code == http.StatusUnauthorized || code == http.StatusForbidden {
a.logger.Debug("API DEBUG",
mlog.Int("code", code),
mlog.Err(sourceError),
mlog.String("msg", message),
mlog.String("api", api),
)
} else {
func (a *API) errorResponse(w http.ResponseWriter, r *http.Request, err error) {
errorResponse := model.ErrorResponse{Error: err.Error()}
switch {
case model.IsErrBadRequest(err):
errorResponse.ErrorCode = http.StatusBadRequest
case model.IsErrUnauthorized(err):
errorResponse.ErrorCode = http.StatusUnauthorized
case model.IsErrForbidden(err):
errorResponse.ErrorCode = http.StatusForbidden
case model.IsErrNotFound(err):
errorResponse.ErrorCode = http.StatusNotFound
case model.IsErrRequestEntityTooLarge(err):
errorResponse.ErrorCode = http.StatusRequestEntityTooLarge
case model.IsErrNotImplemented(err):
errorResponse.ErrorCode = http.StatusNotImplemented
default:
a.logger.Error("API ERROR",
mlog.Int("code", code),
mlog.Err(sourceError),
mlog.String("msg", message),
mlog.String("api", api),
mlog.Int("code", http.StatusInternalServerError),
mlog.Err(err),
mlog.String("api", r.URL.Path),
)
errorResponse.Error = "internal server error"
errorResponse.ErrorCode = http.StatusInternalServerError
}
setResponseHeader(w, "Content-Type", "application/json")
if sourceError != nil && message != sourceError.Error() {
message += "; " + sourceError.Error()
}
data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: code})
data, err := json.Marshal(errorResponse)
if err != nil {
data = []byte("{}")
}
w.WriteHeader(code)
w.WriteHeader(errorResponse.ErrorCode)
_, _ = w.Write(data)
}

78
server/api/api_test.go Normal file
View File

@ -0,0 +1,78 @@
package api
import (
"database/sql"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/stretchr/testify/require"
pluginapi "github.com/mattermost/mattermost-plugin-api"
)
func TestErrorResponse(t *testing.T) {
testAPI := API{logger: mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)}
testCases := []struct {
Name string
Error error
ResponseCode int
ResponseBody string
}{
// bad request
{"ErrBadRequest", model.NewErrBadRequest("bad field"), http.StatusBadRequest, "bad field"},
{"ErrViewsLimitReached", model.ErrViewsLimitReached, http.StatusBadRequest, "limit reached"},
{"ErrAuthParam", model.NewErrAuthParam("password is required"), http.StatusBadRequest, "password is required"},
{"ErrInvalidCategory", model.NewErrInvalidCategory("open"), http.StatusBadRequest, "open"},
{"ErrBoardMemberIsLastAdmin", model.ErrBoardMemberIsLastAdmin, http.StatusBadRequest, "no admins"},
{"ErrBoardIDMismatch", model.ErrBoardIDMismatch, http.StatusBadRequest, "Board IDs do not match"},
// unauthorized
{"ErrUnauthorized", model.NewErrUnauthorized("not enough permissions"), http.StatusUnauthorized, "not enough permissions"},
// forbidden
{"ErrForbidden", model.NewErrForbidden("not enough permissions"), http.StatusForbidden, "not enough permissions"},
{"ErrPermission", model.NewErrPermission("not enough permissions"), http.StatusForbidden, "not enough permissions"},
{"ErrPatchUpdatesLimitedCards", model.ErrPatchUpdatesLimitedCards, http.StatusForbidden, "cards that are limited"},
{"ErrCategoryPermissionDenied", model.ErrCategoryPermissionDenied, http.StatusForbidden, "doesn't belong to user"},
// not found
{"ErrNotFound", model.NewErrNotFound("board"), http.StatusNotFound, "board"},
{"ErrNotAllFound", model.NewErrNotAllFound("block", []string{"1", "2"}), http.StatusNotFound, "not all instances of {block} in {1, 2} found"},
{"sql.ErrNoRows", sql.ErrNoRows, http.StatusNotFound, "rows"},
{"mattermost-plugin-api/ErrNotFound", pluginapi.ErrNotFound, http.StatusNotFound, "not found"},
{"ErrNotFound", model.ErrCategoryDeleted, http.StatusNotFound, "category is deleted"},
// request entity too large
{"ErrRequestEntityTooLarge", model.ErrRequestEntityTooLarge, http.StatusRequestEntityTooLarge, "entity too large"},
// not implemented
{"ErrNotFound", model.ErrInsufficientLicense, http.StatusNotImplemented, "appropriate license required"},
{"ErrNotImplemented", model.NewErrNotImplemented("not implemented in plugin mode"), http.StatusNotImplemented, "plugin mode"},
// internal server error
{"Any other error", ErrHandlerPanic, http.StatusInternalServerError, "internal server error"},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s should be a %d code", tc.Name, tc.ResponseCode), func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/test", nil)
w := httptest.NewRecorder()
testAPI.errorResponse(w, r, tc.Error)
res := w.Result()
require.Equal(t, tc.ResponseCode, res.StatusCode)
require.Equal(t, "application/json", res.Header.Get("Content-Type"))
b, rErr := io.ReadAll(res.Body)
require.NoError(t, rErr)
res.Body.Close()
require.Contains(t, string(b), tc.ResponseBody)
})
}
}

View File

@ -56,7 +56,7 @@ func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
@ -66,11 +66,7 @@ func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
@ -85,7 +81,7 @@ func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Transfer-Encoding", "binary")
if err := a.app.ExportArchive(w, opts); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
}
auditRec.Success()
@ -130,17 +126,17 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
teamID := vars["teamID"]
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
@ -166,7 +162,7 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
mlog.String("team_id", teamID),
mlog.Err(err),
)
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -202,7 +198,8 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
vars := mux.Vars(r)
@ -218,13 +215,13 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
ids := []string{}
@ -243,7 +240,7 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Transfer-Encoding", "binary")
if err := a.app.ExportArchive(w, opts); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
}
auditRec.Success()

View File

@ -3,7 +3,7 @@ package api
import (
"context"
"encoding/json"
"io/ioutil"
"io"
"net"
"net/http"
"strings"
@ -57,30 +57,26 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
}
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
if len(a.singleUserToken) > 0 {
// Not permitted in single-user mode
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "not permitted in single-user mode", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode"))
return
}
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var loginData model.LoginRequest
err = json.Unmarshal(requestBody, &loginData)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -92,12 +88,12 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
if loginData.Type == "normal" {
token, err := a.app.Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "incorrect login", err)
a.errorResponse(w, r, model.NewErrUnauthorized("incorrect login"))
return
}
json, err := json.Marshal(model.LoginResponse{Token: token})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -106,7 +102,7 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
return
}
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid login type", nil)
a.errorResponse(w, r, model.NewErrBadRequest("invalid login type"))
}
func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) {
@ -127,17 +123,13 @@ func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
}
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
if len(a.singleUserToken) > 0 {
// Not permitted in single-user mode
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "not permitted in single-user mode", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode"))
return
}
@ -150,7 +142,7 @@ func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("userID", session.UserID)
if err := a.app.Logout(session.ID); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "incorrect login", err)
a.errorResponse(w, r, model.NewErrUnauthorized("incorrect logout"))
return
}
@ -185,30 +177,26 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
}
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
if len(a.singleUserToken) > 0 {
// Not permitted in single-user mode
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "not permitted in single-user mode", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode"))
return
}
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var registerData model.RegisterRequest
err = json.Unmarshal(requestBody, &registerData)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
registerData.Email = strings.TrimSpace(registerData.Email)
@ -218,29 +206,29 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
if len(registerData.Token) > 0 {
team, err2 := a.app.GetRootTeam()
if err2 != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
a.errorResponse(w, r, err2)
return
}
if registerData.Token != team.SignupToken {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid token", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("invalid token"))
return
}
} else {
// No signup token, check if no active users
userCount, err2 := a.app.GetRegisteredUserCount()
if err2 != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
a.errorResponse(w, r, err2)
return
}
if userCount > 0 {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "no sign-up token and user(s) already exist", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("no sign-up token and user(s) already exist"))
return
}
}
if err = registerData.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, err)
return
}
@ -250,7 +238,7 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
err = a.app.RegisterUser(registerData.Username, registerData.Email, registerData.Password)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -292,37 +280,33 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
}
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
if len(a.singleUserToken) > 0 {
// Not permitted in single-user mode
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "not permitted in single-user mode", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode"))
return
}
vars := mux.Vars(r)
userID := vars["userID"]
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var requestData model.ChangePasswordRequest
if err = json.Unmarshal(requestBody, &requestData); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if err = requestData.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, err)
return
}
@ -330,7 +314,7 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {
defer a.audit.LogRecord(audit.LevelAuth, auditRec)
if err = a.app.ChangePassword(userID, requestData.OldPassword, requestData.NewPassword); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -349,7 +333,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
a.logger.Debug(`attachSession`, mlog.Bool("single_user", len(a.singleUserToken) > 0))
if len(a.singleUserToken) > 0 {
if required && (token != a.singleUserToken) {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid single user token", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("invalid single user token"))
return
}
@ -389,7 +373,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
session, err := a.app.GetSession(token)
if err != nil {
if required {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", err)
a.errorResponse(w, r, model.NewErrUnauthorized(err.Error()))
return
}
@ -404,7 +388,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
mlog.String("want", a.authService),
mlog.String("got", authService),
)
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", err)
a.errorResponse(w, r, model.NewErrUnauthorized(err.Error()))
return
}
@ -418,7 +402,7 @@ func (a *API) adminRequired(handler func(w http.ResponseWriter, r *http.Request)
// Currently, admin APIs require local unix connections
conn := GetContextConn(r)
if _, isUnix := conn.(*net.UnixConn); !isUnix {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "not a local unix connection", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("not a local unix connection"))
return
}

View File

@ -2,14 +2,12 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/app"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
@ -78,29 +76,25 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)
if userID == "" && !hasValidReadToken {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrUnauthorized("access denied to board"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "Board not found", nil)
a.errorResponse(w, r, err)
return
}
if !hasValidReadToken {
if board.IsTemplate && board.Type == model.BoardTypeOpen {
if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board template"))
return
}
} else {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
}
@ -108,12 +102,12 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
var isGuest bool
isGuest, err = a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"guest are not allowed to get board templates"})
a.errorResponse(w, r, model.NewErrPermission("guest are not allowed to get board templates"))
return
}
}
@ -133,27 +127,26 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
case all != "":
blocks, err = a.app.GetBlocksForBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
case blockID != "":
block, err = a.app.GetBlockByID(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if block != nil {
if block.BoardID != boardID {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
message := fmt.Sprintf("block ID=%s on BoardID=%s", block.ID, boardID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
blocks = append(blocks, *block)
}
default:
blocks, err = a.app.GetBlocks(boardID, parentID, blockType)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
}
@ -169,13 +162,13 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
var bErr error
blocks, bErr = a.app.ApplyCloudLimits(blocks)
if bErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", bErr)
a.errorResponse(w, r, err)
return
}
json, err := json.Marshal(blocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -234,9 +227,9 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
val := r.URL.Query().Get("disable_notify")
disableNotify := val == True
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -244,7 +237,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
err = json.Unmarshal(requestBody, &blocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -254,7 +247,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
// Error checking
if len(block.Type) < 1 {
message := fmt.Sprintf("missing type for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
@ -266,32 +259,32 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
if block.CreateAt < 1 {
message := fmt.Sprintf("invalid createAt for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if block.UpdateAt < 1 {
message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if block.BoardID != boardID {
message := fmt.Sprintf("invalid BoardID for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
}
if hasContents {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrPermission("access denied to make board changes"))
return
}
}
if hasComments {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to post card comments"})
a.errorResponse(w, r, model.NewErrPermission("access denied to post card comments"))
return
}
}
@ -311,19 +304,14 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
sourceBoardID := r.URL.Query().Get("sourceBoardID")
if sourceBoardID != "" {
if updateFileIDsErr := a.app.CopyCardFiles(sourceBoardID, blocks); updateFileIDsErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", updateFileIDsErr)
a.errorResponse(w, r, updateFileIDsErr)
return
}
}
newBlocks, err := a.app.InsertBlocksAndNotify(blocks, session.UserID, disableNotify)
if err != nil {
if errors.Is(err, app.ErrViewsLimitReached) {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
} else {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
}
a.errorResponse(w, r, err)
return
}
@ -334,7 +322,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
json, err := json.Marshal(newBlocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -389,17 +377,18 @@ func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {
disableNotify := val == True
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrPermission("access denied to make board changes"))
return
}
block, err := a.app.GetBlockByID(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if block == nil || block.BoardID != boardID {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
if block.BoardID != boardID {
message := fmt.Sprintf("block ID=%s on BoardID=%s", block.ID, boardID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
@ -410,7 +399,7 @@ func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {
err = a.app.DeleteBlockAndNotify(blockID, userID, disableNotify)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -463,31 +452,24 @@ func (a *API) handleUndeleteBlock(w http.ResponseWriter, r *http.Request) {
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
block, err := a.app.GetLastBlockHistoryEntry(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if block == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if board.ID != block.BoardID {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
message := fmt.Sprintf("block ID=%s on BoardID=%s", block.ID, board.ID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modify board members"))
return
}
@ -497,13 +479,13 @@ func (a *API) handleUndeleteBlock(w http.ResponseWriter, r *http.Request) {
undeletedBlock, err := a.app.UndeleteBlock(blockID, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
undeletedBlockData, err := json.Marshal(undeletedBlock)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -564,30 +546,31 @@ func (a *API) handlePatchBlock(w http.ResponseWriter, r *http.Request) {
disableNotify := val == True
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrPermission("access denied to make board changes"))
return
}
block, err := a.app.GetBlockByID(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if block == nil || block.BoardID != boardID {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
if block.BoardID != boardID {
message := fmt.Sprintf("block ID=%s on BoardID=%s", block.ID, boardID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var patch *model.BlockPatch
err = json.Unmarshal(requestBody, &patch)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -596,13 +579,8 @@ func (a *API) handlePatchBlock(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("boardID", boardID)
auditRec.AddMeta("blockID", blockID)
_, err = a.app.PatchBlockAndNotify(blockID, patch, userID, disableNotify)
if errors.Is(err, app.ErrPatchUpdatesLimitedCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
if _, err = a.app.PatchBlockAndNotify(blockID, patch, userID, disableNotify); err != nil {
a.errorResponse(w, r, err)
return
}
@ -657,16 +635,16 @@ func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) {
val := r.URL.Query().Get("disable_notify")
disableNotify := val == True
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var patches *model.BlockPatchBatch
err = json.Unmarshal(requestBody, &patches)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -680,22 +658,18 @@ func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) {
var block *model.Block
block, err = a.app.GetBlockByID(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrForbidden("access denied to make board changes"))
return
}
if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrPermission("access denied to make board changesa"))
return
}
}
err = a.app.PatchBlocksAndNotify(teamID, patches, userID, disableNotify)
if errors.Is(err, app.ErrPatchUpdatesLimitedCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -748,41 +722,35 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) {
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
}
if userID == "" {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrUnauthorized("access denied to board"))
return
}
block, err := a.app.GetBlockByID(blockID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if block == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if board.ID != block.BoardID {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
message := fmt.Sprintf("block ID=%s on BoardID=%s", block.ID, board.ID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
if block.Type == model.TypeComment {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to comment on board cards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to comment on board cards"))
return
}
} else {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board cards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modify board cards"))
return
}
}
@ -799,13 +767,13 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) {
blocks, err := a.app.DuplicateBlock(boardID, blockID, userID, asTemplate == True)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(blocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,12 +2,10 @@ package api
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/app"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
@ -57,7 +55,7 @@ func (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -67,14 +65,14 @@ func (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
// retrieve boards list
boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -85,7 +83,7 @@ func (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(boards)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -125,42 +123,42 @@ func (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var newBoard *model.Board
if err = json.Unmarshal(requestBody, &newBoard); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if newBoard.Type == model.BoardTypeOpen {
if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePublicChannel) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create public boards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create public boards"))
return
}
} else {
if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePrivateChannel) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create private boards"))
return
}
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
if err = newBoard.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -172,7 +170,7 @@ func (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) {
// create board
board, err := a.app.CreateBoard(newBoard, userID, true)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -185,7 +183,7 @@ func (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(board)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -228,42 +226,38 @@ func (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) {
hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)
if userID == "" && !hasValidReadToken {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrUnauthorized("access denied to board"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if !hasValidReadToken {
if board.Type == model.BoardTypePrivate {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
} else {
var isGuest bool
isGuest, err = a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
}
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
}
@ -279,7 +273,7 @@ func (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(board)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -324,49 +318,44 @@ func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
boardID := mux.Vars(r)["boardID"]
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
if _, err := a.app.GetBoard(boardID); err != nil {
a.errorResponse(w, r, err)
return
}
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var patch *model.BoardPatch
if err = json.Unmarshal(requestBody, &patch); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if err = patch.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying board properties"))
return
}
if patch.Type != nil || patch.MinimumRole != nil {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying board type"))
return
}
}
if patch.ChannelID != nil {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board access"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying board access"))
return
}
}
@ -379,7 +368,7 @@ func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) {
// patch board
updatedBoard, err := a.app.PatchBoard(patch, boardID, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -390,7 +379,7 @@ func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(updatedBoard)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -430,18 +419,13 @@ func (a *API) handleDeleteBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
// Check if board exists
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
if _, err := a.app.GetBoard(boardID); err != nil {
a.errorResponse(w, r, err)
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to delete board"))
return
}
@ -450,7 +434,7 @@ func (a *API) handleDeleteBoard(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("boardID", boardID)
if err := a.app.DeleteBoard(boardID, userID); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -495,49 +479,45 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) {
toTeam := query.Get("toTeam")
if userID == "" {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrUnauthorized("access denied to board"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if toTeam == "" && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
if toTeam != "" && !a.permissions.HasPermissionToTeam(userID, toTeam, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
if board.IsTemplate && board.Type == model.BoardTypeOpen {
if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
} else {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
@ -551,13 +531,13 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) {
boardsAndBlocks, _, err := a.app.DuplicateBoard(boardID, userID, toTeam, asTemplate == True)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(boardsAndBlocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -603,13 +583,13 @@ func (a *API) handleUndeleteBoard(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("boardID", boardID)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to undelete board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to undelete board"))
return
}
err := a.app.UndeleteBoard(boardID, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -653,27 +633,23 @@ func (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
board, boardMetadata, err := a.app.GetBoardMetadata(boardID)
if errors.Is(err, app.ErrInsufficientLicense) {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if board == nil || boardMetadata == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, model.NewErrNotFound("board metadata BoardID="+boardID))
return
}
if board.Type == model.BoardTypePrivate {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
} else {
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
}
@ -684,7 +660,7 @@ func (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(boardMetadata)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,13 +2,11 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/app"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
@ -51,22 +49,20 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var newBab *model.BoardsAndBlocks
if err = json.Unmarshal(requestBody, &newBab); err != nil {
// a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, err)
return
}
if len(newBab.Boards) == 0 {
message := "at least one board is required"
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest("at least one board is required"))
return
}
@ -81,30 +77,28 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
}
if teamID != board.TeamID {
message := "cannot create boards for multiple teams"
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest("cannot create boards for multiple teams"))
return
}
if board.ID == "" {
message := "boards need an ID to be referenced from the blocks"
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest("boards need an ID to be referenced from the blocks"))
return
}
}
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board template"))
return
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
@ -112,25 +106,25 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
// Error checking
if len(block.Type) < 1 {
message := fmt.Sprintf("missing type for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if block.CreateAt < 1 {
message := fmt.Sprintf("invalid createAt for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if block.UpdateAt < 1 {
message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if !boardIDs[block.BoardID] {
message := fmt.Sprintf("invalid BoardID %s (not exists in the created boards)", block.BoardID)
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
}
@ -139,7 +133,7 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
// linked and then regenerated by the server
newBab, err = model.GenerateBoardsAndBlocksIDs(newBab, a.logger)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -153,7 +147,7 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
// create boards and blocks
bab, err := a.app.CreateBoardsAndBlocks(newBab, userID, true)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err)
a.errorResponse(w, r, err)
return
}
@ -166,7 +160,7 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
data, err := json.Marshal(bab)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err)
a.errorResponse(w, r, err)
return
}
@ -205,20 +199,20 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var pbab *model.PatchBoardsAndBlocks
if err = json.Unmarshal(requestBody, &pbab); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, err)
return
}
if err = pbab.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -229,29 +223,25 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
patch := pbab.BoardPatches[i]
if err = patch.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying board properties"))
return
}
if patch.Type != nil || patch.MinimumRole != nil {
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying board type"))
return
}
}
board, err2 := a.app.GetBoard(boardID)
if err2 != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, err2)
return
}
@ -259,7 +249,7 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
teamID = board.TeamID
}
if teamID != board.TeamID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("mismatched team ID"))
return
}
}
@ -267,21 +257,17 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
for _, blockID := range pbab.BlockIDs {
block, err2 := a.app.GetBlockByID(blockID)
if err2 != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
return
}
if block == nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, err2)
return
}
if _, ok := boardIDMap[block.BoardID]; !ok {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("missing BoardID="+block.BoardID))
return
}
if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying cards"))
return
}
}
@ -292,12 +278,8 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
auditRec.AddMeta("blocksCount", len(pbab.BlockIDs))
bab, err := a.app.PatchBoardsAndBlocks(pbab, userID)
if errors.Is(err, app.ErrPatchUpdatesLimitedCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -308,7 +290,7 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
data, err := json.Marshal(bab)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -345,15 +327,15 @@ func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var dbab *model.DeleteBoardsAndBlocks
if err = json.Unmarshal(requestBody, &dbab); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -366,24 +348,20 @@ func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request
// all boards in the request should belong to the same team
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, err)
return
}
if teamID == "" {
teamID = board.TeamID
}
if teamID != board.TeamID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("all boards should belong to the same team"))
return
}
// permission check
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to delete board"))
return
}
}
@ -391,27 +369,23 @@ func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request
for _, blockID := range dbab.Blocks {
block, err2 := a.app.GetBlockByID(blockID)
if err2 != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2)
return
}
if block == nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, err2)
return
}
if _, ok := boardIDMap[block.BoardID]; !ok {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("missing BoardID="+block.BoardID))
return
}
if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modifying cards"))
return
}
}
if err := dbab.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -421,7 +395,7 @@ func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request
auditRec.AddMeta("blocksCount", len(dbab.Blocks))
if err := a.app.DeleteBoardsAndBlocks(dbab, userID); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
@ -71,29 +72,29 @@ func (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) {
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "invalid request body", err)
a.errorResponse(w, r, err)
return
}
var newCard *model.Card
if err = json.Unmarshal(requestBody, &newCard); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create card"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create card"))
return
}
if newCard.BoardID != "" && newCard.BoardID != boardID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", model.ErrBoardIDMismatch)
a.errorResponse(w, r, model.ErrBoardIDMismatch)
return
}
newCard.PopulateWithBoardID(boardID)
if err = newCard.CheckValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -104,7 +105,7 @@ func (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) {
// create card
card, err := a.app.CreateCard(newCard, boardID, userID, disableNotify)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -116,7 +117,7 @@ func (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(card)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -171,7 +172,7 @@ func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) {
strPerPage := query.Get("per_page")
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to fetch cards"})
a.errorResponse(w, r, model.NewErrPermission("access denied to fetch cards"))
return
}
@ -184,12 +185,14 @@ func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) {
page, err := strconv.Atoi(strPage)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid `page` parameter", err)
message := fmt.Sprintf("invalid `page` parameter: %s", err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
}
perPage, err := strconv.Atoi(strPerPage)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid `per_page` parameter", err)
message := fmt.Sprintf("invalid `per_page` parameter: %s", err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
}
auditRec := a.makeAuditRecord(r, "getCards", audit.Fail)
@ -200,7 +203,7 @@ func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) {
cards, err := a.app.GetCardsForBoard(boardID, page, perPage)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -214,7 +217,7 @@ func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(cards)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -269,24 +272,25 @@ func (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) {
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
card, err := a.app.GetCardByID(cardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "could not fetch card "+cardID, err)
message := fmt.Sprintf("could not fetch card %s: %s", cardID, err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if !a.permissions.HasPermissionToBoard(userID, card.BoardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to patch card"})
a.errorResponse(w, r, model.NewErrPermission("access denied to patch card"))
return
}
var patch *model.CardPatch
if err = json.Unmarshal(requestBody, &patch); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -298,7 +302,7 @@ func (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) {
// patch card
cardPatched, err := a.app.PatchCard(patch, card.ID, userID, disableNotify)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -310,7 +314,7 @@ func (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(cardPatched)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -351,12 +355,13 @@ func (a *API) handleGetCard(w http.ResponseWriter, r *http.Request) {
card, err := a.app.GetCardByID(cardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "could not fetch card "+cardID, err)
message := fmt.Sprintf("could not fetch card %s: %s", cardID, err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if !a.permissions.HasPermissionToBoard(userID, card.BoardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to fetch card"})
a.errorResponse(w, r, model.NewErrPermission("access denied to fetch card"))
return
}
@ -373,7 +378,7 @@ func (a *API) handleGetCard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(card)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,13 +2,11 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/app"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
)
@ -54,9 +52,9 @@ func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -64,7 +62,7 @@ func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
err = json.Unmarshal(requestBody, &category)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -76,13 +74,8 @@ func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
// user can only create category for themselves
if category.UserID != session.UserID {
a.errorResponse(
w,
r.URL.Path,
http.StatusBadRequest,
fmt.Sprintf("userID %s and category userID %s mismatch", session.UserID, category.UserID),
nil,
)
message := fmt.Sprintf("userID %s and category userID %s mismatch", session.UserID, category.UserID)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
@ -90,25 +83,19 @@ func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
teamID := vars["teamID"]
if category.TeamID != teamID {
a.errorResponse(
w,
r.URL.Path,
http.StatusBadRequest,
"teamID mismatch",
nil,
)
a.errorResponse(w, r, model.NewErrBadRequest("teamID mismatch"))
return
}
createdCategory, err := a.app.CreateCategory(&category)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(createdCategory)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -157,16 +144,16 @@ func (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
categoryID := vars["categoryID"]
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var category model.Category
err = json.Unmarshal(requestBody, &category)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -174,7 +161,7 @@ func (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) {
defer a.audit.LogRecord(audit.LevelModify, auditRec)
if categoryID != category.ID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "categoryID mismatch in patch and body", nil)
a.errorResponse(w, r, model.NewErrBadRequest("categoryID mismatch in patch and body"))
return
}
@ -183,40 +170,25 @@ func (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) {
// user can only update category for themselves
if category.UserID != session.UserID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "user ID mismatch in session and category", nil)
a.errorResponse(w, r, model.NewErrBadRequest("user ID mismatch in session and category"))
return
}
teamID := vars["teamID"]
if category.TeamID != teamID {
a.errorResponse(
w,
r.URL.Path,
http.StatusBadRequest,
"teamID mismatch",
nil,
)
a.errorResponse(w, r, model.NewErrBadRequest("teamID mismatch"))
return
}
updatedCategory, err := a.app.UpdateCategory(&category)
if err != nil {
switch {
case errors.Is(err, app.ErrorCategoryDeleted):
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err)
case errors.Is(err, app.ErrorCategoryPermissionDenied):
// TODO: The permissions should be handled as much as possible at
// the API level, this needs to be changed
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err)
default:
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
}
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(updatedCategory)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -266,22 +238,13 @@ func (a *API) handleDeleteCategory(w http.ResponseWriter, r *http.Request) {
deletedCategory, err := a.app.DeleteCategory(categoryID, userID, teamID)
if err != nil {
switch {
case errors.Is(err, app.ErrorInvalidCategory):
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
case errors.Is(err, app.ErrorCategoryPermissionDenied):
// TODO: The permissions should be handled as much as possible at
// the API level, this needs to be changed
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err)
default:
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
}
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(deletedCategory)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -329,13 +292,13 @@ func (a *API) handleGetUserCategoryBoards(w http.ResponseWriter, r *http.Request
categoryBlocks, err := a.app.GetUserCategoryBoards(userID, teamID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(categoryBlocks)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -392,7 +355,7 @@ func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request)
// TODO: Check the category and the team matches
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
@ -50,7 +51,7 @@ func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
@ -59,12 +60,12 @@ func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
if !a.permissions.HasPermissionToChannel(userID, channelID, model.PermissionReadChannel) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to channel"})
a.errorResponse(w, r, model.NewErrPermission("access denied to channel"))
return
}
@ -75,7 +76,7 @@ func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) {
channel, err := a.app.GetChannel(teamID, channelID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -86,14 +87,15 @@ func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) {
if channel.TeamId != teamID {
if channel.Type != mm_model.ChannelTypeDirect && channel.Type != mm_model.ChannelTypeGroup {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
message := fmt.Sprintf("channel ID=%s on TeamID=%s", channel.Id, teamID)
a.errorResponse(w, r, model.NewErrNotFound(message))
return
}
}
data, err := json.Marshal(channel)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -34,7 +34,7 @@ func (a *API) getClientConfig(w http.ResponseWriter, r *http.Request) {
configData, err := json.Marshal(clientConfig)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
jsonBytesResponse(w, http.StatusOK, configData)

View File

@ -84,22 +84,18 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)
if userID == "" && !hasValidReadToken {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", nil)
a.errorResponse(w, r, model.NewErrUnauthorized("access denied to board"))
return
}
if !hasValidReadToken && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
@ -124,7 +120,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
fileInfo, err := a.app.GetFileInfo(filename)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -139,7 +135,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
data, jsonErr := json.Marshal(fileMetadata)
if jsonErr != nil {
a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr))
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", jsonErr)
a.errorResponse(w, r, jsonErr)
return
}
@ -149,7 +145,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
defer fileReader.Close()
@ -201,17 +197,13 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
a.errorResponse(w, r, model.NewErrPermission("access denied to make board changes"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
@ -222,10 +214,10 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
file, handle, err := r.FormFile(UploadFormFileKey)
if err != nil {
if strings.HasSuffix(err.Error(), "http: request body too large") {
a.errorResponse(w, r.URL.Path, http.StatusRequestEntityTooLarge, "", err)
a.errorResponse(w, r, model.ErrRequestEntityTooLarge)
return
}
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
defer file.Close()
@ -238,7 +230,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
fileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -248,7 +240,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
)
data, err := json.Marshal(FileUploadResponse{FileID: fileID})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
@ -63,7 +64,7 @@ func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
@ -74,7 +75,7 @@ func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) {
timeRange := query.Get("time_range")
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -83,25 +84,28 @@ func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) {
page, err := strconv.Atoi(query.Get("page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err)
message := fmt.Sprintf("error converting page parameter to integer: %s", err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if page < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
a.errorResponse(w, r, model.NewErrBadRequest("Invalid page parameter"))
}
perPage, err := strconv.Atoi(query.Get("per_page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err)
message := fmt.Sprintf("error converting per_page parameter to integer: %s", err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if perPage < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
a.errorResponse(w, r, model.NewErrBadRequest("Invalid page parameter"))
}
userTimezone, aErr := a.app.GetUserTimezone(userID)
if aErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr)
message := fmt.Sprintf("Error getting time zone of user: %s", aErr)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
userLocation, _ := time.LoadLocation(userTimezone)
@ -116,13 +120,13 @@ func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) {
PerPage: perPage,
})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(boardsInsights)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -176,7 +180,7 @@ func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
@ -186,7 +190,7 @@ func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) {
timeRange := query.Get("time_range")
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -194,25 +198,27 @@ func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) {
defer a.audit.LogRecord(audit.LevelRead, auditRec)
page, err := strconv.Atoi(query.Get("page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err)
a.errorResponse(w, r, model.NewErrBadRequest("error converting page parameter to integer"))
return
}
if page < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
a.errorResponse(w, r, model.NewErrBadRequest("Invalid page parameter"))
}
perPage, err := strconv.Atoi(query.Get("per_page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err)
message := fmt.Sprintf("error converting per_page parameter to integer: %s", err)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
if perPage < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
a.errorResponse(w, r, model.NewErrBadRequest("Invalid page parameter"))
}
userTimezone, aErr := a.app.GetUserTimezone(userID)
if aErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr)
message := fmt.Sprintf("Error getting time zone of user: %s", aErr)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
userLocation, _ := time.LoadLocation(userTimezone)
@ -227,12 +233,12 @@ func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) {
PerPage: perPage,
})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(boardsInsights)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
jsonBytesResponse(w, http.StatusOK, data)

View File

@ -5,6 +5,7 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/model"
)
func (a *API) registerLimitsRoutes(r *mux.Router) {
@ -35,13 +36,13 @@ func (a *API) handleCloudLimits(w http.ResponseWriter, r *http.Request) {
boardsCloudLimits, err := a.app.GetBoardsCloudLimits()
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(boardsCloudLimits)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -73,7 +74,7 @@ func (a *API) handleNotifyAdminUpgrade(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", errAPINotSupportedInStandaloneMode)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}

View File

@ -2,12 +2,10 @@ package api
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/app"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/audit"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
@ -55,7 +53,7 @@ func (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board members"})
a.errorResponse(w, r, model.NewErrPermission("access denied to board members"))
return
}
@ -65,7 +63,7 @@ func (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) {
members, err := a.app.GetMembersForBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -76,7 +74,7 @@ func (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(members)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -125,33 +123,29 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modify board members"))
return
}
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var reqBoardMember *model.BoardMember
if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
if reqBoardMember.UserID == "" {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -169,7 +163,7 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {
member, err := a.app.AddMemberToBoard(newBoardMember)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -180,7 +174,7 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(member)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -222,37 +216,33 @@ func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if userID == "" {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("missing user ID"))
return
}
boardID := mux.Vars(r)["boardID"]
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
if board.Type != model.BoardTypeOpen {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
a.errorResponse(w, r, model.NewErrPermission("cannot join a non Open board"))
return
}
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"guests not allowed to join boards"})
a.errorResponse(w, r, model.NewErrPermission("guests not allowed to join boards"))
return
}
@ -272,7 +262,7 @@ func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {
member, err := a.app.AddMemberToBoard(newBoardMember)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -283,7 +273,7 @@ func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(member)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -323,24 +313,20 @@ func (a *API) handleLeaveBoard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if userID == "" {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil)
a.errorResponse(w, r, model.NewErrBadRequest("invalid session"))
return
}
boardID := mux.Vars(r)["boardID"]
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
return
}
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, err)
return
}
@ -350,12 +336,8 @@ func (a *API) handleLeaveBoard(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("addedUserID", userID)
err = a.app.DeleteBoardMember(boardID, userID)
if errors.Is(err, app.ErrBoardMemberIsLastAdmin) {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -410,15 +392,15 @@ func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
paramsUserID := mux.Vars(r)["userID"]
userID := getUserID(r)
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var reqBoardMember *model.BoardMember
if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
@ -433,7 +415,7 @@ func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(paramsUserID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -442,7 +424,7 @@ func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modify board members"))
return
}
@ -452,12 +434,8 @@ func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("patchedUserID", paramsUserID)
member, err := a.app.UpdateBoardMember(newBoardMember)
if errors.Is(err, app.ErrBoardMemberIsLastAdmin) {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
return
}
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -468,7 +446,7 @@ func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(member)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -513,18 +491,13 @@ func (a *API) handleDeleteMember(w http.ResponseWriter, r *http.Request) {
paramsUserID := mux.Vars(r)["userID"]
userID := getUserID(r)
board, err := a.app.GetBoard(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if board == nil {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err)
if _, err := a.app.GetBoard(boardID); err != nil {
a.errorResponse(w, r, err)
return
}
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"})
a.errorResponse(w, r, model.NewErrPermission("access denied to modify board members"))
return
}
@ -534,12 +507,8 @@ func (a *API) handleDeleteMember(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("addedUserID", paramsUserID)
deleteErr := a.app.DeleteBoardMember(boardID, paramsUserID)
if errors.Is(deleteErr, app.ErrBoardMemberIsLastAdmin) {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", deleteErr)
return
}
if deleteErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", deleteErr)
a.errorResponse(w, r, deleteErr)
return
}

View File

@ -49,23 +49,23 @@ func (a *API) handleOnboard(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
return
}
teamID, boardID, err := a.app.PrepareOnboardingTour(userID, teamID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -75,7 +75,7 @@ func (a *API) handleOnboard(w http.ResponseWriter, r *http.Request) {
}
data, err := json.Marshal(response)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -52,7 +52,7 @@ func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
@ -63,7 +63,7 @@ func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -73,7 +73,7 @@ func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {
channels, err := a.app.SearchUserChannels(teamID, userID, searchQuery)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -84,7 +84,7 @@ func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(channels)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -133,7 +133,7 @@ func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -148,14 +148,14 @@ func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
// retrieve boards list
boards, err := a.app.SearchBoardsForUser(term, userID, !isGuest)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -166,7 +166,7 @@ func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(boards)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -212,7 +212,7 @@ func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request)
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
@ -221,7 +221,7 @@ func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request)
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -237,7 +237,7 @@ func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request)
// retrieve boards list
boards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -255,7 +255,7 @@ func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request)
data, err := json.Marshal(linkableBoards)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -307,14 +307,14 @@ func (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
// retrieve boards list
boards, err := a.app.SearchBoardsForUser(term, userID, !isGuest)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -324,7 +324,7 @@ func (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(boards)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,7 +2,8 @@ package api
import (
"encoding/json"
"io/ioutil"
"errors"
"io"
"net/http"
"github.com/gorilla/mux"
@ -12,6 +13,8 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
var ErrTurningOnSharing = errors.New("turning on sharing for board failed, see log for details")
func (a *API) registerSharingRoutes(r *mux.Router) {
// Sharing APIs
r.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handlePostSharing)).Methods("POST")
@ -51,7 +54,7 @@ func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to sharing the board"))
return
}
@ -61,17 +64,13 @@ func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) {
sharing, err := a.app.GetSharing(boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
if sharing == nil {
jsonStringResponse(w, http.StatusOK, "")
a.errorResponse(w, r, err)
return
}
sharingData, err := json.Marshal(sharing)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -121,20 +120,20 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"})
a.errorResponse(w, r, model.NewErrPermission("access denied to sharing the board"))
return
}
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var sharing model.Sharing
err = json.Unmarshal(requestBody, &sharing)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -162,7 +161,7 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
"Attempt to turn on sharing for board via API failed, sharing off in configuration.",
mlog.String("boardID", sharing.ID),
mlog.String("userID", userID))
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "Turning on sharing for board failed, see log for details.", nil)
a.errorResponse(w, r, ErrTurningOnSharing)
return
}
@ -170,7 +169,7 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
err = a.app.UpsertSharing(sharing)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,7 +2,8 @@ package api
import (
"encoding/json"
"io/ioutil"
"fmt"
"io"
"net/http"
"github.com/gorilla/mux"
@ -48,22 +49,22 @@ func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var sub model.Subscription
err = json.Unmarshal(requestBody, &sub)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
if err = json.Unmarshal(requestBody, &sub); err != nil {
a.errorResponse(w, r, err)
return
}
if err = sub.IsValid(); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, model.NewErrBadRequest(err.Error()))
return
}
ctx := r.Context()
@ -76,20 +77,21 @@ func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) {
// User can only create subscriptions for themselves (for now)
if session.UserID != sub.SubscriberID {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "userID and subscriberID mismatch", nil)
a.errorResponse(w, r, model.NewErrBadRequest("userID and subscriberID mismatch"))
return
}
// check for valid block
block, err := a.app.GetBlockByID(sub.BlockID)
if err != nil || block == nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid blockID", err)
_, bErr := a.app.GetBlockByID(sub.BlockID)
if bErr != nil {
message := fmt.Sprintf("invalid blockID: %s", bErr)
a.errorResponse(w, r, model.NewErrBadRequest(message))
return
}
subNew, err := a.app.CreateSubscription(&sub)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -100,7 +102,7 @@ func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) {
json, err := json.Marshal(subNew)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -151,13 +153,12 @@ func (a *API) handleDeleteSubscription(w http.ResponseWriter, r *http.Request) {
// User can only delete subscriptions for themselves
if session.UserID != subscriberID {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil)
a.errorResponse(w, r, model.NewErrPermission("access denied"))
return
}
_, err := a.app.DeleteSubscription(blockID, subscriberID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
if _, err := a.app.DeleteSubscription(blockID, subscriberID); err != nil {
a.errorResponse(w, r, err)
return
}
@ -209,13 +210,13 @@ func (a *API) handleGetSubscriptions(w http.ResponseWriter, r *http.Request) {
// User can only get subscriptions for themselves (for now)
if session.UserID != subscriberID {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil)
a.errorResponse(w, r, model.NewErrPermission("access denied"))
return
}
subs, err := a.app.GetSubscriptions(subscriberID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -226,7 +227,7 @@ func (a *API) handleGetSubscriptions(w http.ResponseWriter, r *http.Request) {
json, err := json.Marshal(subs)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
jsonBytesResponse(w, http.StatusOK, json)

View File

@ -44,7 +44,7 @@ func (a *API) handleGetTeams(w http.ResponseWriter, r *http.Request) {
teams, err := a.app.GetTeamsForUser(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
}
auditRec := a.makeAuditRecord(r, "getTeams", audit.Fail)
@ -53,7 +53,7 @@ func (a *API) handleGetTeams(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(teams)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -92,7 +92,7 @@ func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -101,17 +101,16 @@ func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) {
if a.MattermostAuth {
team, err = a.app.GetTeam(teamID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
if model.IsErrNotFound(err) {
a.errorResponse(w, r, model.NewErrUnauthorized("invalid team"))
}
if team == nil {
a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid team", nil)
return
if err != nil {
a.errorResponse(w, r, err)
}
} else {
team, err = a.app.GetRootTeam()
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
}
@ -122,7 +121,7 @@ func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(team)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -154,13 +153,13 @@ func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http
// schema:
// "$ref": "#/definitions/ErrorResponse"
if a.MattermostAuth {
a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil)
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
return
}
team, err := a.app.GetRootTeam()
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -169,9 +168,8 @@ func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http
team.SignupToken = utils.NewID(utils.IDTypeToken)
err = a.app.UpsertTeamSignupToken(*team)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
if err = a.app.UpsertTeamSignupToken(*team); err != nil {
a.errorResponse(w, r, err)
return
}
@ -225,7 +223,7 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
excludeBots := r.URL.Query().Get("exclude_bots") == True
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
@ -234,7 +232,7 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
asGuestUser := ""
@ -244,13 +242,13 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
users, err := a.app.SearchTeamUsers(teamID, searchQuery, asGuestUser, excludeBots)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "searchQuery="+searchQuery, err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(users)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -47,17 +47,17 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
if teamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"})
a.errorResponse(w, r, model.NewErrPermission("access denied to team"))
return
}
isGuest, err := a.userIsGuest(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if isGuest {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to templates"})
a.errorResponse(w, r, model.NewErrPermission("access denied to templates"))
return
}
@ -68,7 +68,7 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) {
// retrieve boards list
boards, err := a.app.GetTemplateBoards(teamID, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -88,7 +88,7 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(results)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -2,7 +2,7 @@ package api
import (
"encoding/json"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/mux"
@ -47,15 +47,15 @@ func (a *API) handleGetUsersList(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var userIDs []string
if err = json.Unmarshal(requestBody, &userIDs); err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -64,13 +64,13 @@ func (a *API) handleGetUsersList(w http.ResponseWriter, r *http.Request) {
users, err := a.app.GetUsersList(userIDs)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err)
a.errorResponse(w, r, err)
return
}
usersList, err := json.Marshal(users)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
a.errorResponse(w, r, err)
return
}
@ -120,14 +120,15 @@ func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) {
} else {
user, err = a.app.GetUser(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
// ToDo: wrap with an invalid token error
a.errorResponse(w, r, err)
return
}
}
userData, err := json.Marshal(user)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
jsonBytesResponse(w, http.StatusOK, userData)
@ -166,13 +167,13 @@ func (a *API) handleGetMyMemberships(w http.ResponseWriter, r *http.Request) {
members, err := a.app.GetMembersForUser(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
membersData, err := json.Marshal(members)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -216,7 +217,7 @@ func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) {
user, err := a.app.GetUser(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -225,17 +226,17 @@ func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) {
canSeeUser, err := a.app.CanSeeUser(session.UserID, userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
if !canSeeUser {
a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil)
a.errorResponse(w, r, model.NewErrNotFound("user ID="+userID))
return
}
userData, err := json.Marshal(user)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -273,16 +274,16 @@ func (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) {
// schema:
// "$ref": "#/definitions/ErrorResponse"
requestBody, err := ioutil.ReadAll(r.Body)
requestBody, err := io.ReadAll(r.Body)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
var patch *model.UserPropPatch
err = json.Unmarshal(requestBody, &patch)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -297,19 +298,19 @@ func (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) {
// a user can update only own config
if userID != session.UserID {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil)
a.errorResponse(w, r, model.NewErrForbidden(""))
return
}
updatedConfig, err := a.app.UpdateUserConfig(userID, *patch)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(updatedConfig)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
@ -344,13 +345,13 @@ func (a *API) handleGetUserPreferences(w http.ResponseWriter, r *http.Request) {
preferences, err := a.app.GetUserPreferences(userID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}
data, err := json.Marshal(preferences)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
a.errorResponse(w, r, err)
return
}

View File

@ -82,7 +82,7 @@ func (a *App) Login(username, email, password, mfaToken string) (string, error)
if username != "" {
var err error
user, err = a.store.GetUserByUsername(username)
if err != nil {
if err != nil && !model.IsErrNotFound(err) {
a.metrics.IncrementLoginFailCount(1)
return "", errors.Wrap(err, "invalid username or password")
}
@ -91,11 +91,12 @@ func (a *App) Login(username, email, password, mfaToken string) (string, error)
if user == nil && email != "" {
var err error
user, err = a.store.GetUserByEmail(email)
if err != nil {
if err != nil && model.IsErrNotFound(err) {
a.metrics.IncrementLoginFailCount(1)
return "", errors.Wrap(err, "invalid username or password")
}
}
if user == nil {
a.metrics.IncrementLoginFailCount(1)
return "", errors.New("invalid username or password")
@ -148,7 +149,10 @@ func (a *App) RegisterUser(username, email, password string) error {
if username != "" {
var err error
user, err = a.store.GetUserByUsername(username)
if err == nil && user != nil {
if err != nil && !model.IsErrNotFound(err) {
return err
}
if user != nil {
return errors.New("The username already exists")
}
}
@ -156,7 +160,10 @@ func (a *App) RegisterUser(username, email, password string) error {
if user == nil && email != "" {
var err error
user, err = a.store.GetUserByEmail(email)
if err == nil && user != nil {
if err != nil && !model.IsErrNotFound(err) {
return err
}
if user != nil {
return errors.New("The email already exists")
}
}

View File

@ -108,7 +108,7 @@ func TestRegisterUser(t *testing.T) {
th.Store.EXPECT().GetUserByUsername("existingUsername").Return(mockUser, nil)
th.Store.EXPECT().GetUserByUsername("newUsername").Return(mockUser, errors.New("user not found"))
th.Store.EXPECT().GetUserByEmail("existingEmail").Return(mockUser, nil)
th.Store.EXPECT().GetUserByEmail("newEmail").Return(nil, errors.New("email not found"))
th.Store.EXPECT().GetUserByEmail("newEmail").Return(nil, model.NewErrNotFound("user"))
th.Store.EXPECT().CreateUser(gomock.Any()).Return(nil)
for _, test := range testcases {

View File

@ -13,8 +13,6 @@ import (
)
var ErrBlocksFromMultipleBoards = errors.New("the block set contain blocks from multiple boards")
var ErrViewsLimitReached = errors.New("views limit reached for board")
var ErrPatchUpdatesLimitedCards = errors.New("patch updates cards that are limited")
func (a *App) GetBlocks(boardID, parentID string, blockType string) ([]model.Block, error) {
if boardID == "" {
@ -81,7 +79,7 @@ func (a *App) PatchBlockAndNotify(blockID string, blockPatch *model.BlockPatch,
return nil, lErr
}
if containsLimitedBlocks {
return nil, ErrPatchUpdatesLimitedCards
return nil, model.ErrPatchUpdatesLimitedCards
}
}
@ -132,7 +130,7 @@ func (a *App) PatchBlocksAndNotify(teamID string, blockPatches *model.BlockPatch
return err
}
if containsLimitedBlocks {
return ErrPatchUpdatesLimitedCards
return model.ErrPatchUpdatesLimitedCards
}
}
@ -249,7 +247,7 @@ func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, d
if !withinLimit {
a.logger.Info("views limit reached on board", mlog.String("board_id", blocks[i].ParentID), mlog.String("team_id", board.TeamID))
return nil, ErrViewsLimitReached
return nil, model.ErrViewsLimitReached
}
}
@ -438,13 +436,12 @@ func (a *App) UndeleteBlock(blockID string, modifiedBy string) (*model.Block, er
}
block, err := a.store.GetBlock(blockID)
if err != nil {
if model.IsErrNotFound(err) {
a.logger.Error("Error loading the block after a successful undelete, not propagating through websockets or notifications", mlog.String("blockID", blockID))
return nil, err
}
if block == nil {
a.logger.Error("Error loading the block after undelete, not propagating through websockets or notifications")
return nil, nil
if err != nil {
return nil, err
}
board, err := a.store.GetBoard(block.BoardID)
@ -540,7 +537,10 @@ func (a *App) getBoardAndCard(block *model.Block) (board *model.Board, card *mod
}
iter, err = a.store.GetBlock(iter.ParentID)
if err != nil || iter == nil {
if model.IsErrNotFound(err) {
return board, card, nil
}
if err != nil {
return board, card, err
}
}

View File

@ -109,7 +109,7 @@ func TestPatchBlocks(t *testing.T) {
th.Store.EXPECT().GetLicense().Return(fakeLicense)
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(150), nil)
err := th.App.PatchBlocks("team-id", &blockPatches, "user-id-1")
require.ErrorIs(t, err, ErrPatchUpdatesLimitedCards)
require.ErrorIs(t, err, model.ErrPatchUpdatesLimitedCards)
})
}

View File

@ -15,9 +15,7 @@ import (
)
var (
ErrBoardMemberIsLastAdmin = errors.New("cannot leave a board with no admins")
ErrNewBoardCannotHaveID = errors.New("new board cannot have an ID")
ErrInsufficientLicense = errors.New("appropriate license required")
)
const linkBoardMessage = "@%s linked Board [%s](%s) with this channel"
@ -25,9 +23,6 @@ const unlinkBoardMessage = "@%s unlinked Board [%s](%s) with this channel"
func (a *App) GetBoard(boardID string) (*model.Board, error) {
board, err := a.store.GetBoard(boardID)
if model.IsErrNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
@ -37,24 +32,19 @@ func (a *App) GetBoard(boardID string) (*model.Board, error) {
func (a *App) GetBoardMetadata(boardID string) (*model.Board, *model.BoardMetadata, error) {
license := a.store.GetLicense()
if license == nil || !(*license.Features.Compliance) {
return nil, nil, ErrInsufficientLicense
return nil, nil, model.ErrInsufficientLicense
}
board, err := a.GetBoard(boardID)
if err != nil {
return nil, nil, err
}
if board == nil {
if model.IsErrNotFound(err) {
// Board may have been deleted, retrieve most recent history instead
board, err = a.getBoardHistory(boardID, true)
if err != nil {
return nil, nil, err
}
}
if board == nil {
// Board not found
return nil, nil, nil
if err != nil {
return nil, nil, err
}
earliestTime, _, err := a.getBoardDescendantModifiedInfo(boardID, false)
@ -319,7 +309,7 @@ func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*mode
board, err := a.store.GetBoard(boardID)
if model.IsErrNotFound(err) {
return nil, model.NewErrNotFound(boardID)
return nil, model.NewErrNotFound("board ID=" + boardID)
}
if err != nil {
return nil, err
@ -519,7 +509,7 @@ func (a *App) UpdateBoardMember(member *model.BoardMember) (*model.BoardMember,
return nil, err2
}
if isLastAdmin {
return nil, ErrBoardMemberIsLastAdmin
return nil, model.ErrBoardMemberIsLastAdmin
}
}
@ -575,7 +565,7 @@ func (a *App) DeleteBoardMember(boardID, userID string) error {
return err
}
if isLastAdmin {
return ErrBoardMemberIsLastAdmin
return model.ErrBoardMemberIsLastAdmin
}
}

View File

@ -70,7 +70,7 @@ func (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID stri
return nil, cErr
}
if containsLimitedBlocks {
return nil, ErrPatchUpdatesLimitedCards
return nil, model.ErrPatchUpdatesLimitedCards
}
}

View File

@ -1,18 +1,10 @@
package app
import (
"errors"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/utils"
)
var (
ErrorCategoryPermissionDenied = errors.New("category doesn't belong to user")
ErrorCategoryDeleted = errors.New("category is deleted")
ErrorInvalidCategory = errors.New("invalid category")
)
func (a *App) CreateCategory(category *model.Category) (*model.Category, error) {
category.Hydrate()
if err := category.IsValid(); err != nil {
@ -43,11 +35,11 @@ func (a *App) UpdateCategory(category *model.Category) (*model.Category, error)
}
if existingCategory.DeleteAt != 0 {
return nil, ErrorCategoryDeleted
return nil, model.ErrCategoryDeleted
}
if existingCategory.UserID != category.UserID {
return nil, ErrorCategoryPermissionDenied
return nil, model.ErrCategoryPermissionDenied
}
category.UpdateAt = utils.GetMillis()
@ -84,12 +76,12 @@ func (a *App) DeleteCategory(categoryID, userID, teamID string) (*model.Category
// verify if category belongs to the user
if existingCategory.UserID != userID {
return nil, ErrorCategoryPermissionDenied
return nil, model.ErrCategoryPermissionDenied
}
// verify if category belongs to the team
if existingCategory.TeamID != teamID {
return nil, ErrorInvalidCategory
return nil, model.NewErrInvalidCategory("category doesn't belong to the team")
}
if err = a.store.DeleteCategory(categoryID, userID, teamID); err != nil {

View File

@ -6,9 +6,6 @@ import (
func (a *App) GetSharing(boardID string) (*model.Sharing, error) {
sharing, err := a.store.GetSharing(boardID)
if model.IsErrNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}

View File

@ -43,15 +43,15 @@ func TestGetSharing(t *testing.T) {
require.Equal(t, "sharing not found", err.Error())
})
t.Run("should return a tuple of nil", func(t *testing.T) {
t.Run("should return a not found error", func(t *testing.T) {
th.Store.EXPECT().GetSharing("test-id").Return(
nil,
sql.ErrNoRows,
)
result, err := th.App.GetSharing("test-id")
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, result)
require.NoError(t, err)
})
}

View File

@ -142,7 +142,7 @@ func (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* eta
if rp.StatusCode >= http.StatusMultipleChoices {
defer closeBody(rp)
b, err := ioutil.ReadAll(rp.Body)
b, err := io.ReadAll(rp.Body)
if err != nil {
return rp, fmt.Errorf("error when parsing response with code %d: %w", rp.StatusCode, err)
}
@ -856,7 +856,7 @@ func (c *Client) ExportBoardArchive(boardID string) ([]byte, *Response) {
}
defer closeBody(r)
buf, err := ioutil.ReadAll(r.Body)
buf, err := io.ReadAll(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, err)
}

View File

@ -1068,7 +1068,8 @@ func TestDeleteBoard(t *testing.T) {
require.True(t, success)
dbBoard, err := th.Server.App().GetBoard(board.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, dbBoard)
})
}
@ -1098,7 +1099,8 @@ func TestUndeleteBoard(t *testing.T) {
require.False(t, success)
dbBoard, err := th.Server.App().GetBoard(board.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, dbBoard)
})
@ -1123,7 +1125,8 @@ func TestUndeleteBoard(t *testing.T) {
require.False(t, success)
dbBoard, err := th.Server.App().GetBoard(board.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, dbBoard)
})
@ -1156,7 +1159,8 @@ func TestUndeleteBoard(t *testing.T) {
require.False(t, success)
dbBoard, err := th.Server.App().GetBoard(board.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, dbBoard)
})

View File

@ -303,7 +303,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
BoardPatches: []*model.BoardPatch{
{Title: &newTitle},
},
BlockIDs: []string{block1.ID, "board-id-2"},
BlockIDs: []string{block1.ID, block2.ID},
BlockPatches: []*model.BlockPatch{
{Title: &newTitle},
{Title: &newTitle},
@ -674,7 +674,7 @@ func TestDeleteBoardsAndBlocks(t *testing.T) {
th := SetupTestHelper(t).InitBasic()
defer th.TearDown()
// a board is required for the permission checks
// a board and a block are required for the permission checks
newBoard := &model.Board{
TeamID: teamID,
Type: model.BoardTypeOpen,
@ -683,9 +683,19 @@ func TestDeleteBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board)
newBlock := model.Block{
ID: "block-id-1",
BoardID: board.ID,
Title: "title",
}
require.NoError(t, th.Server.App().InsertBlock(newBlock, th.GetUser1().ID))
block, err := th.Server.App().GetBlockByID(newBlock.ID)
require.NoError(t, err)
require.NotNil(t, block)
t.Run("no boards", func(t *testing.T) {
dbab := &model.DeleteBoardsAndBlocks{
Blocks: []string{"block-id-1"},
Blocks: []string{block.ID},
}
success, resp := th.Client.DeleteBoardsAndBlocks(dbab)
@ -790,17 +800,21 @@ func TestDeleteBoardsAndBlocks(t *testing.T) {
// ensure that the entities have been successfully deleted
board1, err = th.Server.App().GetBoard("board-id-1")
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, board1)
block1, err = th.Server.App().GetBlockByID("block-id-1")
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, block1)
board2, err = th.Server.App().GetBoard("board-id-2")
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, board2)
block2, err = th.Server.App().GetBlockByID("block-id-2")
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, block2)
})
}

View File

@ -5,7 +5,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"testing"
@ -38,7 +38,7 @@ type TestCase struct {
url string
method string
body string
userRole string // userAnon, userNoTeamMember, userTeamMember, userViewer, userCommenter, userEditor or userAdmin
userRole string // userAnon, userNoTeamMember, userTeamMember, userViewer, userCommenter, userEditor, userAdmin or userGuest
expectedStatusCode int
totalResults int
}
@ -301,7 +301,7 @@ func runTestCases(t *testing.T, ttCases []TestCase, testData TestData, clients C
require.NoError(t, err)
}
if tc.expectedStatusCode >= 200 && tc.expectedStatusCode < 300 {
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
require.Fail(t, err.Error())
}

View File

@ -38,14 +38,13 @@ func TestSharing(t *testing.T) {
boardID = board.ID
s, err := th.Server.App().GetSharing(boardID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, s)
sharing, resp := th.Client.GetSharing(boardID)
require.NoError(t, resp.Error)
require.NotNil(t, sharing)
require.False(t, sharing.Enabled)
require.Empty(t, sharing.ID)
th.CheckNotFound(resp)
require.Nil(t, sharing)
})
t.Run("POST sharing, config = false", func(t *testing.T) {
@ -64,11 +63,8 @@ func TestSharing(t *testing.T) {
t.Run("GET sharing", func(t *testing.T) {
sharing, resp := th.Client.GetSharing(boardID)
// Expect empty sharing object
require.NoError(t, resp.Error)
require.NotNil(t, sharing)
require.False(t, sharing.Enabled)
require.Empty(t, sharing.ID)
require.Empty(t, sharing.Token)
th.CheckNotFound(resp)
require.Nil(t, sharing)
})
})

View File

@ -13,11 +13,17 @@ const (
MinimumPasswordLength = 8
)
type AuthParamError struct {
func NewErrAuthParam(msg string) *ErrAuthParam {
return &ErrAuthParam{
msg: msg,
}
}
type ErrAuthParam struct {
msg string
}
func (pe AuthParamError) Error() string {
func (pe *ErrAuthParam) Error() string {
return pe.msg
}
@ -84,16 +90,16 @@ type RegisterRequest struct {
func (rd *RegisterRequest) IsValid() error {
if strings.TrimSpace(rd.Username) == "" {
return AuthParamError{"username is required"}
return NewErrAuthParam("username is required")
}
if strings.TrimSpace(rd.Email) == "" {
return AuthParamError{"email is required"}
return NewErrAuthParam("email is required")
}
if !auth.IsEmailValid(rd.Email) {
return AuthParamError{"invalid email format"}
return NewErrAuthParam("invalid email format")
}
if rd.Password == "" {
return AuthParamError{"password is required"}
return NewErrAuthParam("password is required")
}
return isValidPassword(rd.Password)
}
@ -113,17 +119,17 @@ type ChangePasswordRequest struct {
// IsValid validates a password change request.
func (rd *ChangePasswordRequest) IsValid() error {
if rd.OldPassword == "" {
return AuthParamError{"old password is required"}
return NewErrAuthParam("old password is required")
}
if rd.NewPassword == "" {
return AuthParamError{"new password is required"}
return NewErrAuthParam("new password is required")
}
return isValidPassword(rd.NewPassword)
}
func isValidPassword(password string) error {
if len(password) < MinimumPasswordLength {
return AuthParamError{fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength)}
return NewErrAuthParam(fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength))
}
return nil
}

View File

@ -52,38 +52,24 @@ func (c *Category) Hydrate() {
func (c *Category) IsValid() error {
if strings.TrimSpace(c.ID) == "" {
return newErrInvalidCategory("category ID cannot be empty")
return NewErrInvalidCategory("category ID cannot be empty")
}
if strings.TrimSpace(c.Name) == "" {
return newErrInvalidCategory("category name cannot be empty")
return NewErrInvalidCategory("category name cannot be empty")
}
if strings.TrimSpace(c.UserID) == "" {
return newErrInvalidCategory("category user ID cannot be empty")
return NewErrInvalidCategory("category user ID cannot be empty")
}
if strings.TrimSpace(c.TeamID) == "" {
return newErrInvalidCategory("category team id ID cannot be empty")
return NewErrInvalidCategory("category team id ID cannot be empty")
}
return nil
}
type ErrInvalidCategory struct {
msg string
}
func newErrInvalidCategory(msg string) *ErrInvalidCategory {
return &ErrInvalidCategory{
msg: msg,
}
}
func (e *ErrInvalidCategory) Error() string {
return e.msg
}
func CategoryFromJSON(data io.Reader) *Category {
var category *Category
_ = json.NewDecoder(data).Decode(&category)

View File

@ -4,78 +4,313 @@ import (
"database/sql"
"errors"
"fmt"
"net/http"
"strings"
mmModel "github.com/mattermost/mattermost-server/v6/model"
pluginapi "github.com/mattermost/mattermost-plugin-api"
)
// ErrBlocksFromDifferentBoards is an error type that can be returned
// when a set of blocks belong to different boards.
var ErrBlocksFromDifferentBoards = errors.New("blocks belong to different boards")
var (
ErrViewsLimitReached = errors.New("views limit reached for board")
ErrPatchUpdatesLimitedCards = errors.New("patch updates cards that are limited")
// ErrNotFound is an error type that can be returned by store APIs when a query unexpectedly fetches no records.
ErrInsufficientLicense = errors.New("appropriate license required")
ErrCategoryPermissionDenied = errors.New("category doesn't belong to user")
ErrCategoryDeleted = errors.New("category is deleted")
ErrBoardMemberIsLastAdmin = errors.New("cannot leave a board with no admins")
ErrRequestEntityTooLarge = errors.New("request entity too large")
)
// ErrNotFound is an error type that can be returned by store APIs
// when a query unexpectedly fetches no records.
type ErrNotFound struct {
resource string
entity string
}
// NewErrNotFound creates a new ErrNotFound instance.
func NewErrNotFound(resource string) *ErrNotFound {
func NewErrNotFound(entity string) *ErrNotFound {
return &ErrNotFound{
resource: resource,
entity: entity,
}
}
func (nf *ErrNotFound) Error() string {
return fmt.Sprintf("{%s} not found", nf.resource)
return fmt.Sprintf("{%s} not found", nf.entity)
}
// IsErrNotFound returns true if `err` is or wraps one of:
// - model.ErrNotFound
// - sql.ErrNoRows
// - mattermost-plugin-api/ErrNotFound.
func IsErrNotFound(err error) bool {
// ErrNotAllFound is an error type that can be returned by store APIs
// when a query that should fetch a certain amount of records
// unexpectedly fetches less.
type ErrNotAllFound struct {
entity string
resources []string
}
func NewErrNotAllFound(entity string, resources []string) *ErrNotAllFound {
return &ErrNotAllFound{
entity: entity,
resources: resources,
}
}
func (naf *ErrNotAllFound) Error() string {
return fmt.Sprintf("not all instances of {%s} in {%s} found", naf.entity, strings.Join(naf.resources, ", "))
}
// ErrBadRequest can be returned when the API handler receives a
// malformed request.
type ErrBadRequest struct {
reason string
}
// NewErrNotFound creates a new ErrNotFound instance.
func NewErrBadRequest(reason string) *ErrBadRequest {
return &ErrBadRequest{
reason: reason,
}
}
func (br *ErrBadRequest) Error() string {
return br.reason
}
// ErrUnauthorized can be returned when requester has provided an
// invalid authorization for a given resource or has not provided any.
type ErrUnauthorized struct {
reason string
}
// NewErrUnauthorized creates a new ErrUnauthorized instance.
func NewErrUnauthorized(reason string) *ErrUnauthorized {
return &ErrUnauthorized{
reason: reason,
}
}
func (br *ErrUnauthorized) Error() string {
return br.reason
}
// ErrPermission can be returned when requester lacks a permission for
// a given resource.
type ErrPermission struct {
reason string
}
// NewErrPermission creates a new ErrPermission instance.
func NewErrPermission(reason string) *ErrPermission {
return &ErrPermission{
reason: reason,
}
}
func (br *ErrPermission) Error() string {
return br.reason
}
// ErrForbidden can be returned when requester doesn't have access to
// a given resource.
type ErrForbidden struct {
reason string
}
// NewErrForbidden creates a new ErrForbidden instance.
func NewErrForbidden(reason string) *ErrForbidden {
return &ErrForbidden{
reason: reason,
}
}
func (br *ErrForbidden) Error() string {
return br.reason
}
type ErrInvalidCategory struct {
msg string
}
func NewErrInvalidCategory(msg string) *ErrInvalidCategory {
return &ErrInvalidCategory{
msg: msg,
}
}
func (e *ErrInvalidCategory) Error() string {
return e.msg
}
type ErrNotImplemented struct {
msg string
}
func NewErrNotImplemented(msg string) *ErrNotImplemented {
return &ErrNotImplemented{
msg: msg,
}
}
func (ni *ErrNotImplemented) Error() string {
return ni.msg
}
// IsErrBadRequest returns true if `err` is or wraps one of:
// - model.ErrBadRequest
// - model.ErrViewsLimitReached
// - model.ErrAuthParam
// - model.ErrInvalidCategory
// - model.ErrBoardMemberIsLastAdmin
// - model.ErrBoardIDMismatch.
func IsErrBadRequest(err error) bool {
if err == nil {
return false
}
// check if this is a sql.ErrNotFound
if errors.Is(err, sql.ErrNoRows) {
// check if this is a model.ErrBadRequest
var br *ErrBadRequest
if errors.As(err, &br) {
return true
}
// check if this is a model.ErrAuthParam
var ap *ErrAuthParam
if errors.As(err, &ap) {
return true
}
// check if this is a model.ErrViewsLimitReached
if errors.Is(err, ErrViewsLimitReached) {
return true
}
// check if this is a model.ErrInvalidCategory
var ic *ErrInvalidCategory
if errors.As(err, &ic) {
return true
}
// check if this is a model.ErrBoardIDMismatch
if errors.Is(err, ErrBoardMemberIsLastAdmin) {
return true
}
// check if this is a model.ErrBoardMemberIsLastAdmin
return errors.Is(err, ErrBoardIDMismatch)
}
// IsErrUnauthorized returns true if `err` is or wraps one of:
// - model.ErrUnauthorized.
func IsErrUnauthorized(err error) bool {
if err == nil {
return false
}
// check if this is a model.ErrUnauthorized
var u *ErrUnauthorized
return errors.As(err, &u)
}
// IsErrForbidden returns true if `err` is or wraps one of:
// - model.ErrForbidden
// - model.ErrPermission
// - model.ErrPatchUpdatesLimitedCards
// - model.ErrorCategoryPermissionDenied.
func IsErrForbidden(err error) bool {
if err == nil {
return false
}
// check if this is a model.ErrForbidden
var f *ErrForbidden
if errors.As(err, &f) {
return true
}
// check if this is a model.ErrPermission
var p *ErrPermission
if errors.As(err, &p) {
return true
}
// check if this is a model.ErrPatchUpdatesLimitedCards
if errors.Is(err, ErrPatchUpdatesLimitedCards) {
return true
}
// check if this is a model.ErrCategoryPermissionDenied
return errors.Is(err, ErrCategoryPermissionDenied)
}
// IsErrNotFound returns true if `err` is or wraps one of:
// - model.ErrNotFound
// - model.ErrNotAllFound
// - sql.ErrNoRows
// - mattermost-plugin-api/ErrNotFound.
// - model.ErrCategoryDeleted.
func IsErrNotFound(err error) bool {
if err == nil {
return false
}
// check if this is a model.ErrNotFound
var nf *ErrNotFound
if errors.As(err, &nf) {
return true
}
// check if this is a model.ErrNotAllFound
var naf *ErrNotAllFound
if errors.As(err, &naf) {
return true
}
// check if this is a sql.ErrNotFound
if errors.Is(err, sql.ErrNoRows) {
return true
}
// check if this is a plugin API error
return errors.Is(err, pluginapi.ErrNotFound)
if errors.Is(err, pluginapi.ErrNotFound) {
return true
}
// ErrNotAllFound is an error type that can be returned by store APIs
// when a query that should fetch a certain amount of records
// unexpectedly fetches less.
type ErrNotAllFound struct {
resources []string
}
func NewErrNotAllFound(resources []string) *ErrNotAllFound {
return &ErrNotAllFound{
resources: resources,
// check if this is a Mattermost AppError with a Not Found status
var appErr *mmModel.AppError
if errors.As(err, &appErr) {
if appErr.StatusCode == http.StatusNotFound {
return true
}
}
func (na *ErrNotAllFound) Error() string {
return fmt.Sprintf("not all instances in {%s} found", strings.Join(na.resources, ", "))
// check if this is a model.ErrCategoryDeleted
return errors.Is(err, ErrCategoryDeleted)
}
// IsErrNotAllFound returns true if `err` is or wraps a ErrNotAllFound.
func IsErrNotAllFound(err error) bool {
// IsErrRequestEntityTooLarge returns true if `err` is or wraps one of:
// - model.ErrRequestEntityTooLarge.
func IsErrRequestEntityTooLarge(err error) bool {
// check if this is a model.ErrRequestEntityTooLarge
return errors.Is(err, ErrRequestEntityTooLarge)
}
// IsErrNotImplemented returns true if `err` is or wraps one of:
// - model.ErrNotImplemented
// - model.ErrInsufficientLicense.
func IsErrNotImplemented(err error) bool {
if err == nil {
return false
}
var na *ErrNotAllFound
return errors.As(err, &na)
// check if this is a model.ErrNotImplemented
var eni *ErrNotImplemented
if errors.As(err, &eni) {
return true
}
// check if this is a model.ErrInsufficientLicense
return errors.Is(err, ErrInsufficientLicense)
}

View File

@ -10,6 +10,7 @@ import (
"go/format"
"go/parser"
"go/token"
"io"
"io/ioutil"
"log"
"os"
@ -126,7 +127,7 @@ func extractStoreMetadata() (*storeMetadata, error) {
if err != nil {
return nil, fmt.Errorf("unable to open store/store.go file: %w", err)
}
src, err := ioutil.ReadAll(file)
src, err := io.ReadAll(file)
if err != nil {
return nil, err
}

View File

@ -4,6 +4,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
@ -348,6 +349,10 @@ func (s *MattermostAuthLayer) GetUsersList(userIDs []string) ([]*model.User, err
return nil, err
}
if len(users) != len(userIDs) {
return users, model.NewErrNotAllFound("user", userIDs)
}
return users, nil
}
@ -497,7 +502,7 @@ func (s *MattermostAuthLayer) GetFileInfo(id string) (*mmModel.FileInfo, error)
var appErr *mmModel.AppError
if errors.As(err, &appErr) {
if appErr.StatusCode == http.StatusNotFound {
return nil, nil
return nil, model.NewErrNotFound("file info ID=" + id)
}
}
@ -757,7 +762,8 @@ func (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model.
if errors.As(memberErr, &appErr) && appErr.StatusCode == http.StatusNotFound {
// Plugin API returns error if channel member doesn't exist.
// We're fine if it doesn't exist, so its not an error for us.
return nil, model.NewErrNotFound(userID)
message := fmt.Sprintf("member BoardID=%s UserID=%s", boardID, userID)
return nil, model.NewErrNotFound(message)
}
return nil, memberErr

View File

@ -26,14 +26,6 @@ func (re BoardIDNilError) Error() string {
return "boardID is nil"
}
type BlockNotFoundErr struct {
blockID string
}
func (be BlockNotFoundErr) Error() string {
return fmt.Sprintf("block not found (block id: %s", be.blockID)
}
func (s *SQLStore) timestampToCharField(name string, as string) string {
switch s.dbType {
case model.MysqlDBType:
@ -136,7 +128,7 @@ func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]model.Block
}
if len(blocks) != len(ids) {
return nil, model.NewErrNotAllFound(ids)
return blocks, model.NewErrNotAllFound("block", ids)
}
return blocks, nil
@ -248,7 +240,7 @@ func (s *SQLStore) insertBlock(db sq.BaseRunner, block *model.Block, userID stri
}
existingBlock, err := s.getBlock(db, block.ID)
if err != nil {
if err != nil && !model.IsErrNotFound(err) {
return err
}
@ -329,9 +321,6 @@ func (s *SQLStore) patchBlock(db sq.BaseRunner, blockID string, blockPatch *mode
if err != nil {
return err
}
if existingBlock == nil {
return BlockNotFoundErr{blockID}
}
block := blockPatch.Patch(existingBlock)
return s.insertBlock(db, block, userID)
@ -364,14 +353,13 @@ func (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []model.Block, userID s
func (s *SQLStore) deleteBlock(db sq.BaseRunner, blockID string, modifiedBy string) error {
block, err := s.getBlock(db, blockID)
if err != nil {
return err
}
if block == nil {
if model.IsErrNotFound(err) {
s.logger.Warn("deleteBlock block not found", mlog.String("block_id", blockID))
return nil // deleting non-exiting block is not considered an error (for now)
}
if err != nil {
return err
}
fieldsJSON, err := json.Marshal(block.Fields)
if err != nil {
@ -432,7 +420,7 @@ func (s *SQLStore) undeleteBlock(db sq.BaseRunner, blockID string, modifiedBy st
if len(blocks) == 0 {
s.logger.Warn("undeleteBlock block not found", mlog.String("block_id", blockID))
return nil // deleting non-exiting block is not considered an error (for now)
return nil // undeleting non-exiting block is not considered an error (for now)
}
block := blocks[0]
@ -548,7 +536,7 @@ func (s *SQLStore) getBlock(db sq.BaseRunner, blockID string) (*model.Block, err
}
if len(blocks) == 0 {
return nil, nil
return nil, model.NewErrNotFound("block ID=" + blockID)
}
return &blocks[0], nil
@ -637,7 +625,7 @@ func (s *SQLStore) getBoardAndCardByID(db sq.BaseRunner, blockID string) (board
}
if len(blocks) == 0 {
return nil, nil, model.NewErrNotFound(blockID)
return nil, nil, model.NewErrNotFound("block history BlockID=" + blockID)
}
return s.getBoardAndCard(db, &blocks[0])
@ -755,7 +743,8 @@ func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID stri
return nil, err
}
if len(blocks) == 0 {
return nil, BlockNotFoundErr{blockID}
message := fmt.Sprintf("block subtree BoardID=%s BlockID=%s", boardID, blockID)
return nil, model.NewErrNotFound(message)
}
var rootBlock model.Block

View File

@ -17,14 +17,6 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
type BoardNotFoundErr struct {
boardID string
}
func (be BoardNotFoundErr) Error() string {
return fmt.Sprintf("board not found (board id: %s", be.boardID)
}
func boardFields(prefix string) []string {
fields := []string{
"id",
@ -231,7 +223,7 @@ func (s *SQLStore) getBoardsFieldsByCondition(db sq.BaseRunner, fields []string,
rows, err := query.Query()
if err != nil {
s.logger.Error(`getBoardsByCondition ERROR`, mlog.Err(err))
s.logger.Error(`getBoardsFieldsByCondition ERROR`, mlog.Err(err))
return nil, err
}
defer s.CloseRows(rows)
@ -242,7 +234,7 @@ func (s *SQLStore) getBoardsFieldsByCondition(db sq.BaseRunner, fields []string,
}
if len(boards) == 0 {
return nil, sql.ErrNoRows
return nil, model.NewErrNotFound("boards")
}
return boards, nil
@ -297,7 +289,16 @@ func (s *SQLStore) getBoardsInTeamByIds(db sq.BaseRunner, boardIDs []string, tea
}
defer s.CloseRows(rows)
return s.boardsFromRows(rows)
boards, err := s.boardsFromRows(rows)
if err != nil {
return nil, err
}
if len(boards) != len(boardIDs) {
return boards, model.NewErrNotAllFound("board", boardIDs)
}
return boards, nil
}
func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, error) {
@ -410,7 +411,7 @@ func (s *SQLStore) patchBoard(db sq.BaseRunner, boardID string, boardPatch *mode
return nil, err
}
if existingBoard == nil {
return nil, BoardNotFoundErr{boardID}
return nil, model.NewErrNotFound("board ID=" + boardID)
}
board := boardPatch.Patch(existingBoard)
@ -598,7 +599,8 @@ func (s *SQLStore) getMemberForBoard(db sq.BaseRunner, boardID, userID string) (
}
if len(members) == 0 {
return nil, sql.ErrNoRows
message := fmt.Sprintf("board member BoardID=%s UserID=%s", boardID, userID)
return nil, model.NewErrNotFound(message)
}
return members[0], nil

View File

@ -1,7 +1,6 @@
package sqlstore
import (
"database/sql"
"fmt"
sq "github.com/Masterminds/squirrel"
@ -115,9 +114,6 @@ func (s *SQLStore) deleteBoardsAndBlocks(db sq.BaseRunner, dbab *model.DeleteBoa
if err != nil {
return err
}
if block == nil {
return sql.ErrNoRows
}
if _, ok := boardIDMap[block.BoardID]; !ok {
return BlockDoesntBelongToBoardsErr{blockID}

View File

@ -29,7 +29,7 @@ func (s *SQLStore) getCategory(db sq.BaseRunner, id string) (*model.Category, er
}
if len(categories) == 0 {
return nil, model.NewErrNotFound(id)
return nil, model.NewErrNotFound("category ID=" + id)
}
return &categories[0], nil

View File

@ -67,29 +67,6 @@ func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID, categoryID,
return s.addUserCategoryBoard(db, userID, categoryID, boardID)
}
/*
func (s *SQLStore) userCategoryBoardExists(db sq.BaseRunner, userID, teamID, categoryID, boardID string) (bool, error) {
query := s.getQueryBuilder(db).
Select("blocks.id").
From(s.tablePrefix + "categories AS categories").
Join(s.tablePrefix + "category_boards AS blocks ON blocks.category_id = categories.id").
Where(sq.Eq{
"user_id": userID,
"team_id": teamID,
"categories.id": categoryID,
"board_id": boardID,
})
rows, err := query.Query()
if err != nil {
s.logger.Error("getCategoryBoard error", mlog.Err(err))
return false, err
}
return rows.Next(), nil
}
*/
func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID, categoryID, boardID string) error {
_, err := s.getQueryBuilder(db).
Insert(s.tablePrefix+"category_boards").

View File

@ -127,8 +127,8 @@ func idsFromRows(rows *sql.Rows) ([]string, error) {
return deleteIds, nil
}
// genericRetentionPoliciesDeletion actually executes the DELETE query using a sq.SelectBuilder
// which selects the rows to delete.
// genericRetentionPoliciesDeletion actually executes the DELETE query
// using a sq.SelectBuilder which selects the rows to delete.
func (s *SQLStore) genericRetentionPoliciesDeletion(
db sq.BaseRunner,
info RetentionTableDeletionInfo,

View File

@ -5,11 +5,14 @@ import (
"errors"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/focalboard/server/model"
mmModel "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *model.FileInfo) error {
func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) error {
query := s.getQueryBuilder(db).
Insert(s.tablePrefix+"file_info").
Columns(
@ -44,7 +47,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *model.FileInfo) erro
return nil
}
func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*model.FileInfo, error) {
func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo, error) {
query := s.getQueryBuilder(db).
Select(
"id",
@ -60,7 +63,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*model.FileInfo, er
row := query.QueryRow()
fileInfo := model.FileInfo{}
fileInfo := mmModel.FileInfo{}
err := row.Scan(
&fileInfo.Id,
@ -74,7 +77,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*model.FileInfo, er
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
return nil, model.NewErrNotFound("file info ID=" + id)
}
s.logger.Error("error scanning fileinfo row", mlog.String("id", id), mlog.Err(err))

View File

@ -102,7 +102,7 @@ func (s *SQLStore) deleteNotificationHint(db sq.BaseRunner, blockID string) erro
}
if count == 0 {
return model.NewErrNotFound(blockID)
return model.NewErrNotFound("notification hint BlockID=" + blockID)
}
return nil
@ -134,7 +134,7 @@ func (s *SQLStore) getNotificationHint(db sq.BaseRunner, blockID string) (*model
return nil, err
}
if len(hint) == 0 {
return nil, model.NewErrNotFound(blockID)
return nil, model.NewErrNotFound("notification hint BlockID=" + blockID)
}
return hint[0], nil
}
@ -165,7 +165,7 @@ func (s *SQLStore) getNextNotificationHint(db sq.BaseRunner, remove bool) (*mode
return nil, err
}
if len(hints) == 0 {
return nil, model.NewErrNotFound("")
return nil, model.NewErrNotFound("next notification hint")
}
hint := hints[0]
@ -186,7 +186,7 @@ func (s *SQLStore) getNextNotificationHint(db sq.BaseRunner, remove bool) (*mode
if rows == 0 {
// another node likely has grabbed this hint for processing concurrently; let that node handle it
// and we'll return an error here so we try again.
return nil, model.NewErrNotFound(hint.BlockID)
return nil, model.NewErrNotFound("notification hint")
}
}

View File

@ -5,6 +5,7 @@ package sqlstore
import (
"database/sql"
"fmt"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/focalboard/server/model"
@ -113,7 +114,8 @@ func (s *SQLStore) deleteSubscription(db sq.BaseRunner, blockID string, subscrib
}
if count == 0 {
return model.NewErrNotFound(blockID + "," + subscriberID)
message := fmt.Sprintf("subscription BlockID=%s SubscriberID=%s", blockID, subscriberID)
return model.NewErrNotFound(message)
}
return nil
@ -149,7 +151,8 @@ func (s *SQLStore) getSubscription(db sq.BaseRunner, blockID string, subscriberI
return nil, err
}
if len(subscriptions) == 0 {
return nil, model.NewErrNotFound(blockID + "," + subscriberID)
message := fmt.Sprintf("subscription BlockID=%s SubscriberID=%s", blockID, subscriberID)
return nil, model.NewErrNotFound(message)
}
return subscriptions[0], nil
}

View File

@ -201,9 +201,5 @@ func (s *SQLStore) getAllTeams(db sq.BaseRunner) ([]*model.Team, error) {
return nil, err
}
if len(teams) == 0 {
return nil, sql.ErrNoRows
}
return teams, nil
}

View File

@ -52,7 +52,7 @@ func (s *SQLStore) getUserByCondition(db sq.BaseRunner, condition sq.Eq) (*model
}
if len(users) == 0 {
return nil, nil
return nil, model.NewErrNotFound("user")
}
return users[0], nil
@ -94,7 +94,7 @@ func (s *SQLStore) getUsersByCondition(db sq.BaseRunner, condition interface{},
}
if len(users) == 0 {
return nil, sql.ErrNoRows
return nil, model.NewErrNotFound("user")
}
return users, nil
@ -105,7 +105,16 @@ func (s *SQLStore) getUserByID(db sq.BaseRunner, userID string) (*model.User, er
}
func (s *SQLStore) getUsersList(db sq.BaseRunner, userIDs []string) ([]*model.User, error) {
return s.getUsersByCondition(db, sq.Eq{"id": userIDs}, 0)
users, err := s.getUsersByCondition(db, sq.Eq{"id": userIDs}, 0)
if err != nil {
return nil, err
}
if len(users) != len(userIDs) {
return users, model.NewErrNotAllFound("user", userIDs)
}
return users, nil
}
func (s *SQLStore) getUserByEmail(db sq.BaseRunner, email string) (*model.User, error) {
@ -215,11 +224,21 @@ func (s *SQLStore) updateUserPasswordByID(db sq.BaseRunner, userID, password str
}
func (s *SQLStore) getUsersByTeam(db sq.BaseRunner, _ string, _ string) ([]*model.User, error) {
return s.getUsersByCondition(db, nil, 0)
users, err := s.getUsersByCondition(db, nil, 0)
if model.IsErrNotFound(err) {
return []*model.User{}, nil
}
return users, err
}
func (s *SQLStore) searchUsersByTeam(db sq.BaseRunner, _ string, searchQuery string, _ string, _ bool) ([]*model.User, error) {
return s.getUsersByCondition(db, &sq.Like{"username": "%" + searchQuery + "%"}, 10)
users, err := s.getUsersByCondition(db, &sq.Like{"username": "%" + searchQuery + "%"}, 10)
if model.IsErrNotFound(err) {
return []*model.User{}, nil
}
return users, err
}
func (s *SQLStore) usersFromRows(rows *sql.Rows) ([]*model.User, error) {

View File

@ -24,10 +24,6 @@ func (s *SQLStore) IsErrNotFound(err error) bool {
return model.IsErrNotFound(err)
}
func (s *SQLStore) IsErrNotAllFound(err error) bool {
return model.IsErrNotAllFound(err)
}
func (s *SQLStore) MarshalJSONB(data interface{}) ([]byte, error) {
b, err := json.Marshal(data)
if err != nil {

View File

@ -259,7 +259,9 @@ func testPatchBlock(t *testing.T, store store.Store) {
t.Run("not existing block id", func(t *testing.T) {
err := store.PatchBlock("invalid-block-id", &model.BlockPatch{}, "user-id-1")
require.Error(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.True(t, model.IsErrNotFound(err))
blocks, err := store.GetBlocksForBoard(boardID)
require.NoError(t, err)
@ -407,7 +409,8 @@ func testPatchBlocks(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
err := store.PatchBlocks(&model.BlockPatchBatch{BlockIDs: blockIds, BlockPatches: blockPatches}, "user-id-1")
require.Error(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
retrievedBlock, err := store.GetBlock("id-test")
require.NoError(t, err)
@ -489,7 +492,7 @@ func testGetSubTree2(t *testing.T, store store.Store) {
t.Run("from not existing id", func(t *testing.T) {
blocks, err = store.GetSubTree2(boardID, "not-exists", model.QuerySubtreeOptions{})
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
}
@ -590,7 +593,8 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
require.NoError(t, err)
block, err := store.GetBlock("block1")
require.NoError(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, block)
time.Sleep(1 * time.Millisecond)
@ -609,7 +613,8 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
require.NoError(t, err)
block, err := store.GetBlock("block1")
require.NoError(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, block)
// Wait for not colliding the ID+insert_at key
@ -638,7 +643,8 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
require.NoError(t, err)
block, err := store.GetBlock("not-exists")
require.NoError(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, block)
})
}
@ -692,14 +698,14 @@ func testGetBlocks(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksWithParentAndType(boardID, "not-exists", "test")
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
t.Run("not existing type", func(t *testing.T) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksWithParentAndType(boardID, "block1", "not-existing")
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
t.Run("valid parent and type", func(t *testing.T) {
@ -713,7 +719,7 @@ func testGetBlocks(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksWithParent(boardID, "not-exists")
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
t.Run("valid parent", func(t *testing.T) {
@ -727,7 +733,7 @@ func testGetBlocks(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksWithType(boardID, "not-exists")
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
t.Run("valid type", func(t *testing.T) {
@ -741,7 +747,7 @@ func testGetBlocks(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksForBoard("not-exists")
require.NoError(t, err)
require.Len(t, blocks, 0)
require.Empty(t, blocks)
})
t.Run("all blocks of the a board", func(t *testing.T) {
@ -750,6 +756,31 @@ func testGetBlocks(t *testing.T, store store.Store) {
require.NoError(t, err)
require.Len(t, blocks, 5)
})
t.Run("several blocks by ids", func(t *testing.T) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksByIDs([]string{"block2", "block4"})
require.NoError(t, err)
require.Len(t, blocks, 2)
})
t.Run("blocks by ids where some are not found", func(t *testing.T) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksByIDs([]string{"block2", "blockNonexistent"})
var naf *model.ErrNotAllFound
require.ErrorAs(t, err, &naf)
require.True(t, model.IsErrNotFound(err))
require.Len(t, blocks, 1)
})
t.Run("blocks by ids where none are found", func(t *testing.T) {
time.Sleep(1 * time.Millisecond)
blocks, err = store.GetBlocksByIDs([]string{"blockNonexistent1", "blockNonexistent2"})
var naf *model.ErrNotAllFound
require.ErrorAs(t, err, &naf)
require.True(t, model.IsErrNotFound(err))
require.Empty(t, blocks)
})
}
func testGetBlock(t *testing.T, store store.Store) {
@ -776,7 +807,8 @@ func testGetBlock(t *testing.T, store store.Store) {
t.Run("get a non-existing block", func(t *testing.T) {
fetchedBlock, err := store.GetBlock("non-existing-id")
require.NoError(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, fetchedBlock)
})
}
@ -1020,4 +1052,12 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
require.Equal(t, expectedBlock.ID, block.ID)
})
t.Run("get block history of a board with no history", func(t *testing.T) {
opts := model.QueryBlockHistoryOptions{}
blocks, err = store.GetBlockHistoryDescendants("nonexistent-board-id", opts)
require.NoError(t, err)
require.Empty(t, blocks)
})
}

View File

@ -22,6 +22,11 @@ func StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, fu
defer tearDown()
testGetBoardsForUserAndTeam(t, store)
})
t.Run("GetBoardsInTeamByIds", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetBoardsInTeamByIds(t, store)
})
t.Run("InsertBoard", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -62,6 +67,11 @@ func StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, fu
defer tearDown()
testGetMembersForBoard(t, store)
})
t.Run("GetMembersForUser", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetMembersForUser(t, store)
})
t.Run("DeleteMember", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -72,6 +82,11 @@ func StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, fu
defer tearDown()
testSearchBoardsForUser(t, store)
})
t.Run("SearchBoardsForUserInTeam", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testSearchBoardsForUserInTeam(t, store)
})
t.Run("GetBoardHistory", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -105,6 +120,8 @@ func testGetBoard(t *testing.T, store store.Store) {
t.Run("nonexisting board", func(t *testing.T) {
rBoard, err := store.GetBoard("nonexistent-id")
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error")
require.Nil(t, rBoard)
})
@ -113,6 +130,12 @@ func testGetBoard(t *testing.T, store store.Store) {
func testGetBoardsForUserAndTeam(t *testing.T, store store.Store) {
userID := "user-id-1"
t.Run("should return empty list if no results are found", func(t *testing.T) {
boards, err := store.GetBoardsForUserAndTeam(testUserID, testTeamID, true)
require.NoError(t, err)
require.Empty(t, boards)
})
t.Run("should return only the boards of the team that the user is a member of", func(t *testing.T) {
teamID1 := "team-id-1"
teamID2 := "team-id-2"
@ -195,6 +218,61 @@ func testGetBoardsForUserAndTeam(t *testing.T, store store.Store) {
})
}
func testGetBoardsInTeamByIds(t *testing.T, store store.Store) {
t.Run("should return err not all found if one or more of the ids are not found", func(t *testing.T) {
for _, boardID := range []string{"board-id-1", "board-id-2"} {
board := &model.Board{
ID: boardID,
TeamID: testTeamID,
Type: model.BoardTypeOpen,
}
rBoard, _, err := store.InsertBoardWithAdmin(board, testUserID)
require.NoError(t, err)
require.NotNil(t, rBoard)
}
testCases := []struct {
Name string
BoardIDs []string
ExpectedError bool
ExpectedLen int
}{
{
Name: "if none of the IDs are found",
BoardIDs: []string{"nonexistent-1", "nonexistent-2"},
ExpectedError: true,
ExpectedLen: 0,
},
{
Name: "if not all of the IDs are found",
BoardIDs: []string{"nonexistent-1", "board-id-1"},
ExpectedError: true,
ExpectedLen: 1,
},
{
Name: "if all of the IDs are found",
BoardIDs: []string{"board-id-1", "board-id-2"},
ExpectedError: false,
ExpectedLen: 2,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
boards, err := store.GetBoardsInTeamByIds(tc.BoardIDs, testTeamID)
if tc.ExpectedError {
var naf *model.ErrNotAllFound
require.ErrorAs(t, err, &naf)
require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error")
} else {
require.NoError(t, err)
}
require.Len(t, boards, tc.ExpectedLen)
})
}
})
}
func testInsertBoard(t *testing.T, store store.Store) {
userID := testUserID
@ -569,14 +647,22 @@ func testSaveMember(t *testing.T, store store.Store) {
require.NoError(t, err)
require.Len(t, memberHistory, initialMemberHistory)
})
t.Run("should return empty list if no results are found", func(t *testing.T) {
memberHistory, err := store.GetBoardMemberHistory(boardID, "nonexistent-user", 0)
require.NoError(t, err)
require.Empty(t, memberHistory)
})
}
func testGetMemberForBoard(t *testing.T, store store.Store) {
userID := testUserID
boardID := testBoardID
t.Run("should return a no rows error for nonexisting membership", func(t *testing.T) {
t.Run("should return an error not found for nonexisting membership", func(t *testing.T) {
bm, err := store.GetMemberForBoard(boardID, userID)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error")
require.Nil(t, bm)
})
@ -602,7 +688,7 @@ func testGetMemberForBoard(t *testing.T, store store.Store) {
}
func testGetMembersForBoard(t *testing.T, store store.Store) {
t.Run("should return empty if there are no members on a board", func(t *testing.T) {
t.Run("should return empty list if there are no members on a board", func(t *testing.T) {
members, err := store.GetMembersForBoard(testBoardID)
require.NoError(t, err)
require.Empty(t, members)
@ -648,6 +734,14 @@ func testGetMembersForBoard(t *testing.T, store store.Store) {
})
}
func testGetMembersForUser(t *testing.T, store store.Store) {
t.Run("should return empty list if there are no memberships for a user", func(t *testing.T) {
members, err := store.GetMembersForUser(testUserID)
require.NoError(t, err)
require.Empty(t, members)
})
}
func testDeleteMember(t *testing.T, store store.Store) {
userID := testUserID
boardID := testBoardID
@ -819,6 +913,14 @@ func testSearchBoardsForUser(t *testing.T, store store.Store) {
}
}
func testSearchBoardsForUserInTeam(t *testing.T, store store.Store) {
t.Run("should return empty list if there are no resutls", func(t *testing.T) {
boards, err := store.SearchBoardsForUserInTeam("nonexistent-team-id", "", testUserID)
require.NoError(t, err)
require.Empty(t, boards)
})
}
func testUndeleteBoard(t *testing.T, store store.Store) {
userID := testUserID

View File

@ -1,7 +1,6 @@
package storetests
import (
"database/sql"
"fmt"
"testing"
"time"
@ -399,7 +398,7 @@ func testDeleteBoardsAndBlocks(t *testing.T, store store.Store) {
time.Sleep(10 * time.Millisecond)
require.ErrorIs(t, store.DeleteBoardsAndBlocks(dbab, userID), sql.ErrNoRows)
require.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID)))
// all the entities should still exist
rBoard1, err := store.GetBoard(board1.ID)
@ -475,7 +474,7 @@ func testDeleteBoardsAndBlocks(t *testing.T, store store.Store) {
time.Sleep(10 * time.Millisecond)
require.ErrorIs(t, store.DeleteBoardsAndBlocks(dbab, userID), sql.ErrNoRows)
require.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID)))
// all the entities should still exist
rBoard1, err := store.GetBoard(board1.ID)
@ -499,7 +498,7 @@ func testDeleteBoardsAndBlocks(t *testing.T, store store.Store) {
require.NotNil(t, rBlock4)
})
t.Run("should not work properly if all the entities are related", func(t *testing.T) {
t.Run("should work properly if all the entities are related", func(t *testing.T) {
newBoard1 := &model.Board{
ID: utils.NewID(utils.IDTypeBoard),
TeamID: teamID,
@ -551,22 +550,28 @@ func testDeleteBoardsAndBlocks(t *testing.T, store store.Store) {
rBoard1, err := store.GetBoard(board1.ID)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBoard1)
rBlock1, err := store.GetBlock(block1.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBlock1)
rBlock2, err := store.GetBlock(block2.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBlock2)
rBoard2, err := store.GetBoard(board2.ID)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBoard2)
rBlock3, err := store.GetBlock(block3.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBlock3)
rBlock4, err := store.GetBlock(block4.ID)
require.NoError(t, err)
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, rBlock4)
})
}

View File

@ -25,7 +25,7 @@ func StoreTestCategoryStore(t *testing.T, setup func(t *testing.T) (store.Store,
defer tearDown()
testDeleteCategory(t, store)
})
t.Run("GetUserCategoriesCategory", func(t *testing.T) {
t.Run("GetUserCategories", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetUserCategories(t, store)
@ -80,6 +80,14 @@ func testGetCreateCategory(t *testing.T, store store.Store) {
assert.Equal(t, "team_id_1", createdCategory.TeamID)
assert.Equal(t, true, createdCategory.Collapsed)
})
t.Run("get nonexistent category", func(t *testing.T) {
category, err := store.GetCategory("nonexistent")
assert.Error(t, err)
var nf *model.ErrNotFound
assert.ErrorAs(t, err, &nf)
assert.Nil(t, category)
})
}
func testUpdateCategory(t *testing.T, store store.Store) {

View File

@ -101,4 +101,10 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
assert.NotEmpty(t, category1BoardCategory)
assert.Equal(t, 0, len(category3BoardCategory.BoardIDs))
t.Run("get empty category boards", func(t *testing.T) {
userCategoryBoards, err := store.GetUserCategoryBoards("nonexistent-user-id", "nonexistent-team-id")
assert.NoError(t, err)
assert.Empty(t, userCategoryBoards)
})
}

View File

@ -3,7 +3,6 @@
package storetests
import (
"database/sql"
"testing"
"time"
@ -124,13 +123,13 @@ func testRunDataRetention(t *testing.T, store store.Store, batchSize int) {
// GetMemberForBoard throws error on now rows found
member, err := store.GetMemberForBoard(boardID, testUserID)
require.Error(t, err)
require.Equal(t, sql.ErrNoRows, err)
require.True(t, model.IsErrNotFound(err), err)
require.Nil(t, member)
// GetSharing throws error on now rows found
sharing, err := store.GetSharing(boardID)
require.Error(t, err)
require.Equal(t, sql.ErrNoRows, err)
require.True(t, model.IsErrNotFound(err), err)
require.Nil(t, sharing)
category, err := store.GetUserCategoryBoards(boardID, testTeamID)

View File

@ -3,10 +3,12 @@ package storetests
import (
"testing"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/store"
"github.com/mattermost/focalboard/server/utils"
mmModel "github.com/mattermost/mattermost-server/v6/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func StoreTestFileStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
@ -24,15 +26,23 @@ func StoreTestFileStore(t *testing.T, setup func(t *testing.T) (store.Store, fun
}
err := sqlStore.SaveFileInfo(fileInfo)
assert.NoError(t, err)
require.NoError(t, err)
retrievedFileInfo, err := sqlStore.GetFileInfo("file_info_1")
assert.NoError(t, err)
assert.Equal(t, "file_info_1", retrievedFileInfo.Id)
assert.Equal(t, "Dunder Mifflin Sales Report 2022", retrievedFileInfo.Name)
assert.Equal(t, ".sales", retrievedFileInfo.Extension)
assert.Equal(t, int64(112233), retrievedFileInfo.Size)
assert.Equal(t, int64(0), retrievedFileInfo.DeleteAt)
assert.False(t, retrievedFileInfo.Archived)
require.NoError(t, err)
require.Equal(t, "file_info_1", retrievedFileInfo.Id)
require.Equal(t, "Dunder Mifflin Sales Report 2022", retrievedFileInfo.Name)
require.Equal(t, ".sales", retrievedFileInfo.Extension)
require.Equal(t, int64(112233), retrievedFileInfo.Size)
require.Equal(t, int64(0), retrievedFileInfo.DeleteAt)
require.False(t, retrievedFileInfo.Archived)
})
t.Run("should return an error on not found", func(t *testing.T) {
fileInfo, err := sqlStore.GetFileInfo("nonexistent")
require.Error(t, err)
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, fileInfo)
})
}

View File

@ -45,6 +45,12 @@ func testCreateAndGetAndDeleteSession(t *testing.T, store store.Store) {
require.Equal(t, session, got)
})
t.Run("Get nonexistent session", func(t *testing.T) {
got, err := store.GetSession("nonexistent-token", 60*60)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, got)
})
t.Run("DeleteAndGetSession", func(t *testing.T) {
err := store.DeleteSession(session.ID)
require.NoError(t, err)

View File

@ -55,5 +55,6 @@ func testUpsertSharingAndGetSharing(t *testing.T, store store.Store) {
t.Run("Get not existing sharing", func(t *testing.T) {
_, err := store.GetSharing("not-existing")
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
})
}

View File

@ -13,6 +13,7 @@ import (
"github.com/mattermost/focalboard/server/services/store"
)
//nolint:dupl
func StoreTestSubscriptionsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
t.Run("CreateSubscription", func(t *testing.T) {
store, tearDown := setup(t)

View File

@ -17,6 +17,12 @@ import (
)
func StoreTestTeamStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
t.Run("GetTeam", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetTeam(t, store)
})
t.Run("UpsertTeamSignupToken", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -36,6 +42,30 @@ func StoreTestTeamStore(t *testing.T, setup func(t *testing.T) (store.Store, fun
})
}
func testGetTeam(t *testing.T, store store.Store) {
t.Run("Nonexistent team", func(t *testing.T) {
got, err := store.GetTeam("nonexistent-id")
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
require.Nil(t, got)
})
t.Run("Valid team", func(t *testing.T) {
teamID := "0"
team := &model.Team{
ID: teamID,
SignupToken: utils.NewID(utils.IDTypeToken),
}
err := store.UpsertTeamSignupToken(*team)
require.NoError(t, err)
got, err := store.GetTeam(teamID)
require.NoError(t, err)
require.Equal(t, teamID, got.ID)
})
}
func testUpsertTeamSignupToken(t *testing.T, store store.Store) {
t.Run("Insert and update team with signup token", func(t *testing.T) {
teamID := "0"
@ -100,6 +130,12 @@ func testUpsertTeamSettings(t *testing.T, store store.Store) {
}
func testGetAllTeams(t *testing.T, store store.Store) {
t.Run("No teams response", func(t *testing.T) {
got, err := store.GetAllTeams()
require.NoError(t, err)
require.Empty(t, got)
})
t.Run("Insert multiple team and get all teams", func(t *testing.T) {
// insert
teamCount := 10

View File

@ -4,6 +4,7 @@
package storetests
import (
"fmt"
"testing"
"time"
@ -14,11 +15,12 @@ import (
"github.com/mattermost/focalboard/server/utils"
)
//nolint:dupl
func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
t.Run("SetGetSystemSettings", func(t *testing.T) {
t.Run("GetUsersByTeam", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetTeamUsers(t, store)
testGetUsersByTeam(t, store)
})
t.Run("CreateAndGetUser", func(t *testing.T) {
@ -27,6 +29,12 @@ func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, fun
testCreateAndGetUser(t, store)
})
t.Run("GetUsersList", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetUsersList(t, store)
})
t.Run("CreateAndUpdateUser", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -38,6 +46,7 @@ func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, fun
defer tearDown()
testCreateAndGetRegisteredUserCount(t, store)
})
t.Run("TestPatchUserProps", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
@ -45,11 +54,11 @@ func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, fun
})
}
func testGetTeamUsers(t *testing.T, store store.Store) {
t.Run("GetTeamUSers", func(t *testing.T) {
func testGetUsersByTeam(t *testing.T, store store.Store) {
t.Run("GetTeamUsers", func(t *testing.T) {
users, err := store.GetUsersByTeam("team_1", "")
require.Equal(t, 0, len(users))
require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error")
require.NoError(t, err)
userID := utils.NewID(utils.IDTypeUser)
@ -93,6 +102,13 @@ func testCreateAndGetUser(t *testing.T, store store.Store) {
require.Equal(t, user.Email, got.Email)
})
t.Run("GetUserByID nonexistent", func(t *testing.T) {
got, err := store.GetUserByID("nonexistent-id")
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, got)
})
t.Run("GetUserByUsername", func(t *testing.T) {
got, err := store.GetUserByUsername(user.Username)
require.NoError(t, err)
@ -101,6 +117,13 @@ func testCreateAndGetUser(t *testing.T, store store.Store) {
require.Equal(t, user.Email, got.Email)
})
t.Run("GetUserByUsername nonexistent", func(t *testing.T) {
got, err := store.GetUserByID("nonexistent-username")
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, got)
})
t.Run("GetUserByEmail", func(t *testing.T) {
got, err := store.GetUserByEmail(user.Email)
require.NoError(t, err)
@ -108,6 +131,69 @@ func testCreateAndGetUser(t *testing.T, store store.Store) {
require.Equal(t, user.Username, got.Username)
require.Equal(t, user.Email, got.Email)
})
t.Run("GetUserByEmail nonexistent", func(t *testing.T) {
got, err := store.GetUserByID("nonexistent-email")
var nf *model.ErrNotFound
require.ErrorAs(t, err, &nf)
require.Nil(t, got)
})
}
func testGetUsersList(t *testing.T, store store.Store) {
for _, id := range []string{"user1", "user2"} {
user := &model.User{
ID: id,
Username: fmt.Sprintf("%s-username", id),
Email: fmt.Sprintf("%s@sample.com", id),
}
err := store.CreateUser(user)
require.NoError(t, err)
}
testCases := []struct {
Name string
UserIDs []string
ExpectedError bool
ExpectedIDs []string
}{
{
Name: "all of the IDs are found",
UserIDs: []string{"user1", "user2"},
ExpectedError: false,
ExpectedIDs: []string{"user1", "user2"},
},
{
Name: "some of the IDs are found",
UserIDs: []string{"user2", "non-existent"},
ExpectedError: true,
ExpectedIDs: []string{"user2"},
},
{
Name: "none of the IDs are found",
UserIDs: []string{"non-existent-1", "non-existent-2"},
ExpectedError: true,
ExpectedIDs: []string{},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
users, err := store.GetUsersList(tc.UserIDs)
if tc.ExpectedError {
require.Error(t, err)
require.True(t, model.IsErrNotFound(err))
} else {
require.NoError(t, err)
}
userIDs := []string{}
for _, user := range users {
userIDs = append(userIDs, user.ID)
}
require.ElementsMatch(t, tc.ExpectedIDs, userIDs)
})
}
}
func testCreateAndUpdateUser(t *testing.T, store store.Store) {

View File

@ -3,7 +3,7 @@ package webhook
import (
"bytes"
"encoding/json"
"io/ioutil"
"io"
"net/http"
"github.com/mattermost/focalboard/server/model"
@ -24,7 +24,7 @@ func (wh *Client) NotifyUpdate(block model.Block) {
}
for _, url := range wh.config.WebhookUpdate {
resp, _ := http.Post(url, "application/json", bytes.NewBuffer(json)) //nolint:gosec
_, _ = ioutil.ReadAll(resp.Body)
_, _ = io.ReadAll(resp.Body)
resp.Body.Close()
wh.logger.Debug("webhook.NotifyUpdate", mlog.String("url", url))