08c0b7a2fd
* 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
247 lines
6.1 KiB
Go
247 lines
6.1 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
const (
|
|
archiveExtension = ".boardarchive"
|
|
)
|
|
|
|
func (a *API) registerAchivesRoutes(r *mux.Router) {
|
|
// Archive APIs
|
|
r.HandleFunc("/boards/{boardID}/archive/export", a.sessionRequired(a.handleArchiveExportBoard)).Methods("GET")
|
|
r.HandleFunc("/teams/{teamID}/archive/import", a.sessionRequired(a.handleArchiveImport)).Methods("POST")
|
|
r.HandleFunc("/teams/{teamID}/archive/export", a.sessionRequired(a.handleArchiveExportTeam)).Methods("GET")
|
|
}
|
|
|
|
func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {
|
|
// swagger:operation GET /boards/{boardID}/archive/export archiveExportBoard
|
|
//
|
|
// Exports an archive of all blocks for one boards.
|
|
//
|
|
// ---
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: boardID
|
|
// in: path
|
|
// description: Id of board to export
|
|
// required: true
|
|
// type: string
|
|
// security:
|
|
// - BearerAuth: []
|
|
// responses:
|
|
// '200':
|
|
// description: success
|
|
// content:
|
|
// application-octet-stream:
|
|
// type: string
|
|
// format: binary
|
|
// default:
|
|
// description: internal error
|
|
// schema:
|
|
// "$ref": "#/definitions/ErrorResponse"
|
|
|
|
vars := mux.Vars(r)
|
|
boardID := vars["boardID"]
|
|
userID := getUserID(r)
|
|
|
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
|
|
a.errorResponse(w, r, model.NewErrPermission("access denied to board"))
|
|
return
|
|
}
|
|
|
|
auditRec := a.makeAuditRecord(r, "archiveExportBoard", audit.Fail)
|
|
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
|
auditRec.AddMeta("BoardID", boardID)
|
|
|
|
board, err := a.app.GetBoard(boardID)
|
|
if err != nil {
|
|
a.errorResponse(w, r, err)
|
|
return
|
|
}
|
|
|
|
opts := model.ExportArchiveOptions{
|
|
TeamID: board.TeamID,
|
|
BoardIDs: []string{board.ID},
|
|
}
|
|
|
|
filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension)
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
|
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
|
|
if err := a.app.ExportArchive(w, opts); err != nil {
|
|
a.errorResponse(w, r, err)
|
|
}
|
|
|
|
auditRec.Success()
|
|
}
|
|
|
|
func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {
|
|
// swagger:operation POST /teams/{teamID}/archive/import archiveImport
|
|
//
|
|
// Import an archive of boards.
|
|
//
|
|
// ---
|
|
// produces:
|
|
// - application/json
|
|
// consumes:
|
|
// - multipart/form-data
|
|
// parameters:
|
|
// - name: teamID
|
|
// in: path
|
|
// description: Team ID
|
|
// required: true
|
|
// type: string
|
|
// - name: file
|
|
// in: formData
|
|
// description: archive file to import
|
|
// required: true
|
|
// type: file
|
|
// security:
|
|
// - BearerAuth: []
|
|
// responses:
|
|
// '200':
|
|
// description: success
|
|
// default:
|
|
// description: internal error
|
|
// schema:
|
|
// "$ref": "#/definitions/ErrorResponse"
|
|
|
|
ctx := r.Context()
|
|
session, _ := ctx.Value(sessionContextKey).(*model.Session)
|
|
userID := session.UserID
|
|
|
|
vars := mux.Vars(r)
|
|
teamID := vars["teamID"]
|
|
|
|
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
|
|
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
|
|
return
|
|
}
|
|
|
|
isGuest, err := a.userIsGuest(userID)
|
|
if err != nil {
|
|
a.errorResponse(w, r, err)
|
|
return
|
|
}
|
|
if isGuest {
|
|
a.errorResponse(w, r, model.NewErrPermission("access denied to create board"))
|
|
return
|
|
}
|
|
|
|
file, handle, err := r.FormFile(UploadFormFileKey)
|
|
if err != nil {
|
|
fmt.Fprintf(w, "%v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
auditRec := a.makeAuditRecord(r, "import", audit.Fail)
|
|
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
|
auditRec.AddMeta("filename", handle.Filename)
|
|
auditRec.AddMeta("size", handle.Size)
|
|
|
|
opt := model.ImportArchiveOptions{
|
|
TeamID: teamID,
|
|
ModifiedBy: userID,
|
|
}
|
|
|
|
if err := a.app.ImportArchive(file, opt); err != nil {
|
|
a.logger.Debug("Error importing archive",
|
|
mlog.String("team_id", teamID),
|
|
mlog.Err(err),
|
|
)
|
|
a.errorResponse(w, r, err)
|
|
return
|
|
}
|
|
|
|
jsonStringResponse(w, http.StatusOK, "{}")
|
|
auditRec.Success()
|
|
}
|
|
|
|
func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {
|
|
// swagger:operation GET /teams/{teamID}/archive/export archiveExportTeam
|
|
//
|
|
// Exports an archive of all blocks for all the boards in a team.
|
|
//
|
|
// ---
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: teamID
|
|
// in: path
|
|
// description: Id of team
|
|
// required: true
|
|
// type: string
|
|
// security:
|
|
// - BearerAuth: []
|
|
// responses:
|
|
// '200':
|
|
// description: success
|
|
// content:
|
|
// application-octet-stream:
|
|
// type: string
|
|
// format: binary
|
|
// default:
|
|
// description: internal error
|
|
// schema:
|
|
// "$ref": "#/definitions/ErrorResponse"
|
|
if a.MattermostAuth {
|
|
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode"))
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
teamID := vars["teamID"]
|
|
|
|
ctx := r.Context()
|
|
session, _ := ctx.Value(sessionContextKey).(*model.Session)
|
|
userID := session.UserID
|
|
|
|
auditRec := a.makeAuditRecord(r, "archiveExportTeam", audit.Fail)
|
|
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
|
auditRec.AddMeta("TeamID", teamID)
|
|
|
|
isGuest, err := a.userIsGuest(userID)
|
|
if err != nil {
|
|
a.errorResponse(w, r, err)
|
|
return
|
|
}
|
|
|
|
boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest)
|
|
if err != nil {
|
|
a.errorResponse(w, r, err)
|
|
return
|
|
}
|
|
ids := []string{}
|
|
for _, board := range boards {
|
|
ids = append(ids, board.ID)
|
|
}
|
|
|
|
opts := model.ExportArchiveOptions{
|
|
TeamID: teamID,
|
|
BoardIDs: ids,
|
|
}
|
|
|
|
filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension)
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
|
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
|
|
if err := a.app.ExportArchive(w, opts); err != nil {
|
|
a.errorResponse(w, r, err)
|
|
}
|
|
|
|
auditRec.Success()
|
|
}
|