diff --git a/server/api/api.go b/server/api/api.go index 40c0bac0d..10bc75d5b 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -87,14 +87,9 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handlePatchBlock)).Methods("PATCH") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/undelete", a.sessionRequired(a.handleUndeleteBlock)).Methods("POST") - apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/duplicate", a.attachSession(a.handleDuplicateBlock, false)).Methods("POST") apiv1.HandleFunc("/boards/{boardID}/metadata", a.sessionRequired(a.handleGetBoardMetadata)).Methods("GET") - // Import&Export APIs - apiv1.HandleFunc("/boards/{boardID}/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") - apiv1.HandleFunc("/boards/{boardID}/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") - // Member APIs apiv1.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleGetMembersForBoard)).Methods("GET") apiv1.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleAddMember)).Methods("POST") @@ -363,23 +358,6 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func stampModificationMetadata(r *http.Request, blocks []model.Block, auditRec *audit.Record) { - userID := getUserID(r) - if userID == model.SingleUser { - userID = "" - } - - now := utils.GetMillis() - for i := range blocks { - blocks[i].ModifiedBy = userID - blocks[i].UpdateAt = now - - if auditRec != nil { - auditRec.AddMeta("block_"+strconv.FormatInt(int64(i), 10), blocks[i]) - } - } -} - func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) { requestBody, err := ioutil.ReadAll(r.Body) if err != nil { @@ -1219,287 +1197,6 @@ func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func (a *API) handleGetSubTree(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /api/v1/boards/{boardID}/blocks/{blockID}/subtree getSubTree - // - // Returns the blocks of a subtree - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: The ID of the root block of the subtree - // required: true - // type: string - // - name: l - // in: query - // description: The number of levels to return. 2 or 3. Defaults to 2. - // required: false - // type: integer - // minimum: 2 - // maximum: 3 - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - blockID := vars["blockID"] - - if !a.hasValidReadTokenForBoard(r, boardID) && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - - query := r.URL.Query() - levels, err := strconv.ParseInt(query.Get("l"), 10, 32) - if err != nil { - levels = 2 - } - - if levels != 2 && levels != 3 { - a.logger.Error("Invalid levels", mlog.Int64("levels", levels)) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid levels", nil) - return - } - - auditRec := a.makeAuditRecord(r, "getSubTree", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("blockID", blockID) - - blocks, err := a.app.GetSubTree(boardID, blockID, int(levels), model.QuerySubtreeOptions{}) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetSubTree", - mlog.Int64("levels", levels), - mlog.String("boardID", boardID), - mlog.String("blockID", blockID), - mlog.Int("block_count", len(blocks)), - ) - json, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - -func (a *API) handleExport(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /api/v1/boards/{boardID}/blocks/export exportBlocks - // - // Returns all blocks of a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - - query := r.URL.Query() - rootID := query.Get("root_id") - - auditRec := a.makeAuditRecord(r, "export", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("rootID", rootID) - - var blocks []model.Block - var err error - if rootID == "" { - blocks, err = a.app.GetBlocksForBoard(boardID) - } else { - blocks, err = a.app.GetBlocksWithBoardID(boardID) - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("raw blocks", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("rawCount", len(blocks)) - - blocks = filterOrphanBlocks(blocks) - - a.logger.Debug("EXPORT filtered blocks", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("filteredCount", len(blocks)) - - json, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.Success() -} - -func filterOrphanBlocks(blocks []model.Block) (ret []model.Block) { - queue := make([]model.Block, 0) - childrenOfBlockWithID := make(map[string]*[]model.Block) - - // Build the trees from nodes - for _, block := range blocks { - if len(block.ParentID) == 0 { - // Queue root blocks to process first - queue = append(queue, block) - } else { - siblings := childrenOfBlockWithID[block.ParentID] - if siblings != nil { - *siblings = append(*siblings, block) - } else { - siblings := []model.Block{block} - childrenOfBlockWithID[block.ParentID] = &siblings - } - } - } - - // Map the trees to an array, which skips orphaned nodes - blocks = make([]model.Block, 0) - for len(queue) > 0 { - block := queue[0] - queue = queue[1:] // dequeue - blocks = append(blocks, block) - children := childrenOfBlockWithID[block.ID] - if children != nil { - queue = append(queue, *children...) - } - } - - return blocks -} - -func (a *API) handleImport(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /api/v1/boards/{boardID}/blocks/import importBlocks - // - // Import blocks on a given board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: Body - // in: body - // description: array of blocks to import - // required: true - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var blocks []model.Block - - err = json.Unmarshal(requestBody, &blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "import", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - - // all blocks should now be part of the board that they're being - // imported onto - for i := range blocks { - blocks[i].BoardID = boardID - } - - stampModificationMetadata(r, blocks, auditRec) - - if _, err = a.app.InsertBlocks(model.GenerateBlockIDs(blocks, a.logger), userID, false); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - - a.logger.Debug("IMPORT BlockIDs", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - // Sharing func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) { @@ -1799,6 +1496,9 @@ func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } team, err := a.app.GetRootTeam() if err != nil { @@ -2832,8 +2532,7 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { return } - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { + if userID == "" { a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) return } @@ -2848,17 +2547,16 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { 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"}) - return - } - } else { - 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 team"}) - 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"}) + return + } + } else { + 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"}) + return } } @@ -2927,8 +2625,7 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() asTemplate := query.Get("asTemplate") - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { + if userID == "" { a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) return } diff --git a/server/api/archive.go b/server/api/archive.go index 372da3d09..dd5e25bf3 100644 --- a/server/api/archive.go +++ b/server/api/archive.go @@ -105,6 +105,9 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } vars := mux.Vars(r) teamID := vars["teamID"] diff --git a/server/api/auth.go b/server/api/auth.go index b5fa26789..5b60584f6 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -166,6 +166,9 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -228,6 +231,9 @@ func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -278,6 +284,9 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -377,6 +386,9 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode diff --git a/server/app/blocks.go b/server/app/blocks.go index 3cd480764..32c168aab 100644 --- a/server/app/blocks.go +++ b/server/app/blocks.go @@ -234,15 +234,6 @@ func (a *App) CopyCardFiles(sourceBoardID string, blocks []model.Block) error { return nil } -func (a *App) GetSubTree(boardID, blockID string, levels int, opts model.QuerySubtreeOptions) ([]model.Block, error) { - // Only 2 or 3 levels are supported for now - if levels >= 3 { - return a.store.GetSubTree3(boardID, blockID, opts) - } - - return a.store.GetSubTree2(boardID, blockID, opts) -} - func (a *App) GetBlockByID(blockID string) (*model.Block, error) { return a.store.GetBlock(blockID) } diff --git a/server/client/client.go b/server/client/client.go index 789d8f7e3..2d5547c7f 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -164,10 +164,6 @@ func (c *Client) GetBlockRoute(boardID, blockID string) string { return fmt.Sprintf("%s/%s", c.GetBlocksRoute(boardID), blockID) } -func (c *Client) GetSubtreeRoute(boardID, blockID string) string { - return fmt.Sprintf("%s/subtree", c.GetBlockRoute(boardID, blockID)) -} - func (c *Client) GetBoardsRoute() string { return "/boards" } @@ -297,16 +293,6 @@ func (c *Client) DeleteBlock(boardID, blockID string) (bool, *Response) { return true, BuildResponse(r) } -func (c *Client) GetSubtree(boardID, blockID string) ([]model.Block, *Response) { - r, err := c.DoAPIGet(c.GetSubtreeRoute(boardID, blockID), "") - if err != nil { - return nil, BuildErrorResponse(r, err) - } - defer closeBody(r) - - return model.BlocksFromJSON(r.Body), BuildResponse(r) -} - // Boards and blocks. func (c *Client) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks) (*model.BoardsAndBlocks, *Response) { r, err := c.DoAPIPost(c.GetBoardsAndBlocksRoute(), toJSON(bab)) diff --git a/server/integrationtests/blocks_test.go b/server/integrationtests/blocks_test.go index 573bd1144..00eca9cfd 100644 --- a/server/integrationtests/blocks_test.go +++ b/server/integrationtests/blocks_test.go @@ -429,18 +429,4 @@ func TestGetSubtree(t *testing.T) { } require.Contains(t, blockIDs, parentBlockID) }) - - t.Run("Get subtree for parent ID", func(t *testing.T) { - blocks, resp := th.Client.GetSubtree(board.ID, parentBlockID) - require.NoError(t, resp.Error) - require.Len(t, blocks, 3) - - blockIDs := make([]string, len(blocks)) - for i, b := range blocks { - blockIDs[i] = b.ID - } - require.Contains(t, blockIDs, parentBlockID) - require.Contains(t, blockIDs, childBlockID1) - require.Contains(t, blockIDs, childBlockID2) - }) } diff --git a/webapp/cypress/integration/loginActions.ts b/webapp/cypress/integration/loginActions.ts index 34c05578c..7d5c463ca 100644 --- a/webapp/cypress/integration/loginActions.ts +++ b/webapp/cypress/integration/loginActions.ts @@ -8,10 +8,8 @@ describe('Login actions', () => { it('Can perform login/register actions', () => { // Redirects to login page - cy.log('**Redirects to error then login page**') + cy.log('**Redirects to login page (except plugin mode) **') cy.visit('/') - cy.location('pathname').should('eq', '/error') - cy.get('button').contains('Log in').click() cy.location('pathname').should('eq', '/login') cy.get('.LoginPage').contains('Log in') cy.get('#login-username').should('exist') @@ -40,7 +38,7 @@ describe('Login actions', () => { // User should not be logged in automatically cy.log('**User should not be logged in automatically**') cy.visit('/') - cy.location('pathname').should('eq', '/error') + cy.location('pathname').should('eq', '/login') // Can log in registered user cy.log('**Can log in registered user**') diff --git a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap index d1c66a10e..044f8c043 100644 --- a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap +++ b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap @@ -695,7 +695,20 @@ exports[`components/centerPanel return centerPanel and press touch 1 with readon
+ > +