From d3edf2f698c5c5246580e40351ed44868ef8e1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Mon, 16 May 2022 18:09:11 +0200 Subject: [PATCH] Adding the board default role for public boards (#2884) * Adding the default role concept in the backend * Adding the interface part * Fix golang-ci lint errors * Adding local permissions tests * Address PR review comments * Improving the code a bit * Another small fix * Renaming DefaultRole to MinimumRole * Setting the minimum role at minimum to check the permissions per roles in the integration tests * Adding the new minimum role behavior * Fixing some tests Co-authored-by: Mattermod --- server/api/api.go | 15 +- server/integrationtests/board_test.go | 82 ++++- server/integrationtests/permissions_test.go | 279 +++++++++++++++++- server/model/board.go | 38 +++ .../localpermissions/localpermissions.go | 11 + .../mmpermissions/mmpermissions.go | 11 + server/services/store/sqlstore/board.go | 41 ++- .../000021_create_default_board_role.down.sql | 3 + .../000021_create_default_board_role.up.sql | 4 + webapp/src/blocks/board.ts | 3 + .../boardTemplateSelectorItem.test.tsx | 2 + .../shareBoard/teamPermissionsRow.tsx | 51 +++- 12 files changed, 504 insertions(+), 36 deletions(-) create mode 100644 server/services/store/sqlstore/migrations/000021_create_default_board_role.down.sql create mode 100644 server/services/store/sqlstore/migrations/000021_create_default_board_role.up.sql diff --git a/server/api/api.go b/server/api/api.go index 811c98c69..0e4499e89 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -2759,7 +2759,7 @@ func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) { return } - if patch.Type != nil { + 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"}) return @@ -3429,12 +3429,13 @@ func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) { return } - // currently all memberships are created as editors by default - // TODO: Support different public roles newBoardMember := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeEditor: true, + UserID: userID, + BoardID: boardID, + SchemeAdmin: board.MinimumRole == model.BoardRoleAdmin, + SchemeEditor: board.MinimumRole == model.BoardRoleNone || board.MinimumRole == model.BoardRoleEditor, + SchemeCommenter: board.MinimumRole == model.BoardRoleCommenter, + SchemeViewer: board.MinimumRole == model.BoardRoleViewer, } auditRec := a.makeAuditRecord(r, "joinBoard", audit.Fail) @@ -3922,7 +3923,7 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request) return } - if patch.Type != nil { + 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"}) return diff --git a/server/integrationtests/board_test.go b/server/integrationtests/board_test.go index 3dc5fb78a..cc6035f85 100644 --- a/server/integrationtests/board_test.go +++ b/server/integrationtests/board_test.go @@ -1945,7 +1945,83 @@ func TestJoinBoard(t *testing.T) { me := th.GetUser1() - title := "Public board" + title := "Test Public board" + teamID := testTeamID + newBoard := &model.Board{ + Title: title, + Type: model.BoardTypeOpen, + TeamID: teamID, + } + board, resp := th.Client.CreateBoard(newBoard) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, board) + require.NotNil(t, board.ID) + require.Equal(t, title, board.Title) + require.Equal(t, model.BoardTypeOpen, board.Type) + require.Equal(t, teamID, board.TeamID) + require.Equal(t, me.ID, board.CreatedBy) + require.Equal(t, me.ID, board.ModifiedBy) + require.Equal(t, model.BoardRoleNone, board.MinimumRole) + + member, resp := th.Client2.JoinBoard(board.ID) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, member) + require.Equal(t, board.ID, member.BoardID) + require.Equal(t, th.GetUser2().ID, member.UserID) + + s, _ := json.MarshalIndent(member, "", "\t") + t.Log(string(s)) + }) + + t.Run("create and join public board should match the minimumRole in the membership", func(t *testing.T) { + th := SetupTestHelper(t).InitBasic() + defer th.TearDown() + + me := th.GetUser1() + + title := "Public board for commenters" + teamID := testTeamID + newBoard := &model.Board{ + Title: title, + Type: model.BoardTypeOpen, + TeamID: teamID, + MinimumRole: model.BoardRoleCommenter, + } + board, resp := th.Client.CreateBoard(newBoard) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, board) + require.NotNil(t, board.ID) + require.Equal(t, title, board.Title) + require.Equal(t, model.BoardTypeOpen, board.Type) + require.Equal(t, teamID, board.TeamID) + require.Equal(t, me.ID, board.CreatedBy) + require.Equal(t, me.ID, board.ModifiedBy) + + member, resp := th.Client2.JoinBoard(board.ID) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, member) + require.Equal(t, board.ID, member.BoardID) + require.Equal(t, th.GetUser2().ID, member.UserID) + require.False(t, member.SchemeAdmin, "new member should not be admin") + require.False(t, member.SchemeEditor, "new member should not be editor") + require.True(t, member.SchemeCommenter, "new member should be commenter") + require.False(t, member.SchemeViewer, "new member should not be viewer") + + s, _ := json.MarshalIndent(member, "", "\t") + t.Log(string(s)) + }) + + t.Run("create and join public board should match editor role in the membership when MinimumRole is empty", func(t *testing.T) { + th := SetupTestHelper(t).InitBasic() + defer th.TearDown() + + me := th.GetUser1() + + title := "Public board for editors" teamID := testTeamID newBoard := &model.Board{ Title: title, @@ -1969,6 +2045,10 @@ func TestJoinBoard(t *testing.T) { require.NotNil(t, member) require.Equal(t, board.ID, member.BoardID) require.Equal(t, th.GetUser2().ID, member.UserID) + require.False(t, member.SchemeAdmin, "new member should not be admin") + require.True(t, member.SchemeEditor, "new member should be editor") + require.False(t, member.SchemeCommenter, "new member should not be commenter") + require.False(t, member.SchemeViewer, "new member should not be viewer") s, _ := json.MarshalIndent(member, "", "\t") t.Log(string(s)) diff --git a/server/integrationtests/permissions_test.go b/server/integrationtests/permissions_test.go index e2fbcfadc..da715f4e5 100644 --- a/server/integrationtests/permissions_test.go +++ b/server/integrationtests/permissions_test.go @@ -129,23 +129,27 @@ type TestData struct { } func setupData(t *testing.T, th *TestHelper) TestData { - customTemplate1, err := th.Server.App().CreateBoard(&model.Board{Title: "Custom template 1", TeamID: "test-team", IsTemplate: true, Type: model.BoardTypeOpen}, userAdminID, true) + customTemplate1, err := th.Server.App().CreateBoard( + &model.Board{Title: "Custom template 1", TeamID: "test-team", IsTemplate: true, Type: model.BoardTypeOpen, MinimumRole: "viewer"}, + userAdminID, + true, + ) require.NoError(t, err) err = th.Server.App().InsertBlock(model.Block{ID: "block-1", Title: "Test", Type: "card", BoardID: customTemplate1.ID}, userAdminID) require.NoError(t, err) customTemplate2, err := th.Server.App().CreateBoard( - &model.Board{Title: "Custom template 2", TeamID: "test-team", IsTemplate: true, Type: model.BoardTypePrivate}, + &model.Board{Title: "Custom template 2", TeamID: "test-team", IsTemplate: true, Type: model.BoardTypePrivate, MinimumRole: "viewer"}, userAdminID, true) require.NoError(t, err) err = th.Server.App().InsertBlock(model.Block{ID: "block-2", Title: "Test", Type: "card", BoardID: customTemplate2.ID}, userAdminID) require.NoError(t, err) - board1, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 1", TeamID: "test-team", Type: model.BoardTypeOpen}, userAdminID, true) + board1, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 1", TeamID: "test-team", Type: model.BoardTypeOpen, MinimumRole: "viewer"}, userAdminID, true) require.NoError(t, err) err = th.Server.App().InsertBlock(model.Block{ID: "block-3", Title: "Test", Type: "card", BoardID: board1.ID}, userAdminID) require.NoError(t, err) - board2, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 2", TeamID: "test-team", Type: model.BoardTypePrivate}, userAdminID, true) + board2, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 2", TeamID: "test-team", Type: model.BoardTypePrivate, MinimumRole: "viewer"}, userAdminID, true) require.NoError(t, err) rBoard2, err := th.Server.App().GetBoard(board2.ID) @@ -558,6 +562,109 @@ func TestPermissionsPatchBoard(t *testing.T) { }) } +func TestPermissionsPatchBoardType(t *testing.T) { + ttCases := []TestCase{ + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userEditor, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userEditor, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, "{\"type\": \"P\"}", userAdmin, http.StatusOK, 1}, + + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userEditor, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userEditor, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, "{\"type\": \"P\"}", userAdmin, http.StatusOK, 1}, + } + + t.Run("plugin", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("local", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + runTestCases(t, ttCases, testData, clients) + }) +} + +func TestPermissionsPatchBoardMinimumRole(t *testing.T) { + patch := toJSON(t, map[string]model.BoardRole{"minimumRole": model.BoardRoleViewer}) + ttCases := []TestCase{ + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userEditor, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}", methodPatch, patch, userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userEditor, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}", methodPatch, patch, userAdmin, http.StatusOK, 1}, + + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userEditor, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}", methodPatch, patch, userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userEditor, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}", methodPatch, patch, userAdmin, http.StatusOK, 1}, + } + + t.Run("plugin", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("local", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + runTestCases(t, ttCases, testData, clients) + }) +} + func TestPermissionsDeleteBoard(t *testing.T) { ttCases := []TestCase{ {"/boards/{PRIVATE_BOARD_ID}", methodDelete, "", userAnon, http.StatusUnauthorized, 0}, @@ -3095,3 +3202,167 @@ func TestPermissionsBoardArchiveImport(t *testing.T) { runTestCases(t, ttCases, testData, clients) }) } + +func TestPermissionsMinimumRolesApplied(t *testing.T) { + ttCasesF := func(t *testing.T, th *TestHelper, minimumRole model.BoardRole, testData TestData) []TestCase { + counter := 0 + newBlockJSON := func(boardID string) string { + counter++ + return toJSON(t, []*model.Block{{ + ID: fmt.Sprintf("%d", counter), + Title: "Board To Create", + BoardID: boardID, + Type: "card", + CreateAt: model.GetMillis(), + UpdateAt: model.GetMillis(), + }}) + } + _, err := th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.privateBoard.ID, userAdminID) + require.NoError(t, err) + _, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.publicBoard.ID, userAdminID) + require.NoError(t, err) + _, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.privateTemplate.ID, userAdminID) + require.NoError(t, err) + _, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.publicTemplate.ID, userAdminID) + require.NoError(t, err) + + if minimumRole == "viewer" || minimumRole == "commenter" { + return []TestCase{ + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1}, + } + } else { + return []TestCase{ + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusOK, 1}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusOK, 1}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusOK, 1}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusOK, 1}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusOK, 1}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusOK, 1}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1}, + + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusOK, 1}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusOK, 1}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1}, + {"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1}, + } + } + } + + t.Run("plugin", func(t *testing.T) { + t.Run("minimum role viewer", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "viewer", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role commenter", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "commenter", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role editor", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "editor", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role admin", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "admin", testData) + runTestCases(t, ttCases, testData, clients) + }) + }) + t.Run("local", func(t *testing.T) { + t.Run("minimum role viewer", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "viewer", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role commenter", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "commenter", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role editor", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "editor", testData) + runTestCases(t, ttCases, testData, clients) + }) + t.Run("minimum role admin", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + ttCases := ttCasesF(t, th, "admin", testData) + runTestCases(t, ttCases, testData, clients) + }) + }) +} diff --git a/server/model/board.go b/server/model/board.go index 307f51c84..ef50686f2 100644 --- a/server/model/board.go +++ b/server/model/board.go @@ -7,12 +7,21 @@ import ( ) type BoardType string +type BoardRole string const ( BoardTypeOpen BoardType = "O" BoardTypePrivate BoardType = "P" ) +const ( + BoardRoleNone BoardRole = "" + BoardRoleViewer BoardRole = "viewer" + BoardRoleCommenter BoardRole = "commenter" + BoardRoleEditor BoardRole = "editor" + BoardRoleAdmin BoardRole = "admin" +) + // Board groups a set of blocks and its layout // swagger:model type Board struct { @@ -40,6 +49,10 @@ type Board struct { // required: true Type BoardType `json:"type"` + // The minimum role applied when somebody joins the board + // required: true + MinimumRole BoardRole `json:"minimumRole"` + // The title of the board // required: false Title string `json:"title"` @@ -92,6 +105,10 @@ type BoardPatch struct { // required: false Type *BoardType `json:"type"` + // The minimum role applied when somebody joins the board + // required: false + MinimumRole *BoardRole `json:"minimumRole"` + // The title of the board // required: false Title *string `json:"title"` @@ -140,6 +157,10 @@ type BoardMember struct { // required: false Roles string `json:"roles"` + // Minimum role because the board configuration + // required: false + MinimumRole string `json:"minimumRole"` + // Marks the user as an admin of the board // required: true SchemeAdmin bool `json:"schemeAdmin"` @@ -221,6 +242,10 @@ func (p *BoardPatch) Patch(board *Board) *Board { board.Title = *p.Title } + if p.MinimumRole != nil { + board.MinimumRole = *p.MinimumRole + } + if p.Description != nil { board.Description = *p.Description } @@ -296,11 +321,19 @@ func IsBoardTypeValid(t BoardType) bool { return t == BoardTypeOpen || t == BoardTypePrivate } +func IsBoardMinimumRoleValid(r BoardRole) bool { + return r == BoardRoleNone || r == BoardRoleAdmin || r == BoardRoleEditor || r == BoardRoleCommenter || r == BoardRoleViewer +} + func (p *BoardPatch) IsValid() error { if p.Type != nil && !IsBoardTypeValid(*p.Type) { return InvalidBoardErr{"invalid-board-type"} } + if p.MinimumRole != nil && !IsBoardMinimumRoleValid(*p.MinimumRole) { + return InvalidBoardErr{"invalid-board-minimum-role"} + } + return nil } @@ -320,6 +353,11 @@ func (b *Board) IsValid() error { if !IsBoardTypeValid(b.Type) { return InvalidBoardErr{"invalid-board-type"} } + + if !IsBoardMinimumRoleValid(b.MinimumRole) { + return InvalidBoardErr{"invalid-board-minimum-role"} + } + return nil } diff --git a/server/services/permissions/localpermissions/localpermissions.go b/server/services/permissions/localpermissions/localpermissions.go index 304a0aad1..e0680866e 100644 --- a/server/services/permissions/localpermissions/localpermissions.go +++ b/server/services/permissions/localpermissions/localpermissions.go @@ -48,6 +48,17 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod return false } + switch member.MinimumRole { + case "admin": + member.SchemeAdmin = true + case "editor": + member.SchemeEditor = true + case "commenter": + member.SchemeCommenter = true + case "viewer": + member.SchemeViewer = true + } + switch permission { case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard: return member.SchemeAdmin diff --git a/server/services/permissions/mmpermissions/mmpermissions.go b/server/services/permissions/mmpermissions/mmpermissions.go index faf5608a1..89275e4c7 100644 --- a/server/services/permissions/mmpermissions/mmpermissions.go +++ b/server/services/permissions/mmpermissions/mmpermissions.go @@ -78,6 +78,17 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod return false } + switch member.MinimumRole { + case "admin": + member.SchemeAdmin = true + case "editor": + member.SchemeEditor = true + case "commenter": + member.SchemeCommenter = true + case "viewer": + member.SchemeViewer = true + } + switch permission { case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard: return member.SchemeAdmin diff --git a/server/services/store/sqlstore/board.go b/server/services/store/sqlstore/board.go index 80f3e4861..6bb24dc65 100644 --- a/server/services/store/sqlstore/board.go +++ b/server/services/store/sqlstore/board.go @@ -31,6 +31,7 @@ func boardFields(prefix string) []string { "COALESCE(created_by, '')", "modified_by", "type", + "minimum_role", "title", "description", "icon", @@ -67,6 +68,7 @@ func boardHistoryFields() []string { "COALESCE(created_by, '')", "COALESCE(modified_by, '')", "type", + "minimum_role", "COALESCE(title, '')", "COALESCE(description, '')", "COALESCE(icon, '')", @@ -84,13 +86,14 @@ func boardHistoryFields() []string { } var boardMemberFields = []string{ - "board_id", - "user_id", - "roles", - "scheme_admin", - "scheme_editor", - "scheme_commenter", - "scheme_viewer", + "COALESCE(B.minimum_role, '')", + "BM.board_id", + "BM.user_id", + "BM.roles", + "BM.scheme_admin", + "BM.scheme_editor", + "BM.scheme_commenter", + "BM.scheme_viewer", } func (s *SQLStore) boardsFromRows(rows *sql.Rows) ([]*model.Board, error) { @@ -108,6 +111,7 @@ func (s *SQLStore) boardsFromRows(rows *sql.Rows) ([]*model.Board, error) { &board.CreatedBy, &board.ModifiedBy, &board.Type, + &board.MinimumRole, &board.Title, &board.Description, &board.Icon, @@ -149,6 +153,7 @@ func (s *SQLStore) boardMembersFromRows(rows *sql.Rows) ([]*model.BoardMember, e var boardMember model.BoardMember err := rows.Scan( + &boardMember.MinimumRole, &boardMember.BoardID, &boardMember.UserID, &boardMember.Roles, @@ -308,6 +313,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri "modified_by": userID, "type": board.Type, "title": board.Title, + "minimum_role": board.MinimumRole, "description": board.Description, "icon": board.Icon, "show_description": board.ShowDescription, @@ -325,6 +331,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri Where(sq.Eq{"id": board.ID}). Set("modified_by", userID). Set("type", board.Type). + Set("minimum_role", board.MinimumRole). Set("title", board.Title). Set("description", board.Description). Set("icon", board.Icon). @@ -398,6 +405,7 @@ func (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error { "created_by": board.CreatedBy, "modified_by": userID, "type": board.Type, + "minimum_role": board.MinimumRole, "title": board.Title, "description": board.Description, "icon": board.Icon, @@ -536,9 +544,10 @@ func (s *SQLStore) deleteMember(db sq.BaseRunner, boardID, userID string) error func (s *SQLStore) getMemberForBoard(db sq.BaseRunner, boardID, userID string) (*model.BoardMember, error) { query := s.getQueryBuilder(db). Select(boardMemberFields...). - From(s.tablePrefix + "board_members"). - Where(sq.Eq{"board_id": boardID}). - Where(sq.Eq{"user_id": userID}) + From(s.tablePrefix + "board_members AS BM"). + LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id"). + Where(sq.Eq{"BM.board_id": boardID}). + Where(sq.Eq{"BM.user_id": userID}) rows, err := query.Query() if err != nil { @@ -562,8 +571,9 @@ func (s *SQLStore) getMemberForBoard(db sq.BaseRunner, boardID, userID string) ( func (s *SQLStore) getMembersForUser(db sq.BaseRunner, userID string) ([]*model.BoardMember, error) { query := s.getQueryBuilder(db). Select(boardMemberFields...). - From(s.tablePrefix + "board_members"). - Where(sq.Eq{"user_id": userID}) + From(s.tablePrefix + "board_members AS BM"). + LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id"). + Where(sq.Eq{"BM.user_id": userID}) rows, err := query.Query() if err != nil { @@ -583,8 +593,9 @@ func (s *SQLStore) getMembersForUser(db sq.BaseRunner, userID string) ([]*model. func (s *SQLStore) getMembersForBoard(db sq.BaseRunner, boardID string) ([]*model.BoardMember, error) { query := s.getQueryBuilder(db). Select(boardMemberFields...). - From(s.tablePrefix + "board_members"). - Where(sq.Eq{"board_id": boardID}) + From(s.tablePrefix + "board_members AS BM"). + LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id"). + Where(sq.Eq{"BM.board_id": boardID}) rows, err := query.Query() if err != nil { @@ -711,6 +722,7 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st "modified_by", "type", "title", + "minimum_role", "description", "icon", "show_description", @@ -730,6 +742,7 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st board.CreatedBy, modifiedBy, board.Type, + board.MinimumRole, board.Title, board.Description, board.Icon, diff --git a/server/services/store/sqlstore/migrations/000021_create_default_board_role.down.sql b/server/services/store/sqlstore/migrations/000021_create_default_board_role.down.sql new file mode 100644 index 000000000..f8c9d8c4c --- /dev/null +++ b/server/services/store/sqlstore/migrations/000021_create_default_board_role.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE {{.prefix}}boards DROP COLUMN minimum_role; +ALTER TABLE {{.prefix}}boards_history DROP COLUMN minimum_role; + diff --git a/server/services/store/sqlstore/migrations/000021_create_default_board_role.up.sql b/server/services/store/sqlstore/migrations/000021_create_default_board_role.up.sql new file mode 100644 index 000000000..55526109a --- /dev/null +++ b/server/services/store/sqlstore/migrations/000021_create_default_board_role.up.sql @@ -0,0 +1,4 @@ +ALTER TABLE {{.prefix}}boards ADD COLUMN minimum_role VARCHAR(36) NOT NULL DEFAULT ''; +ALTER TABLE {{.prefix}}boards_history ADD COLUMN minimum_role VARCHAR(36) NOT NULL DEFAULT ''; +UPDATE {{.prefix}}boards SET minimum_role = 'editor'; +UPDATE {{.prefix}}boards_history SET minimum_role = 'editor'; diff --git a/webapp/src/blocks/board.ts b/webapp/src/blocks/board.ts index a70a1381c..d4d20b081 100644 --- a/webapp/src/blocks/board.ts +++ b/webapp/src/blocks/board.ts @@ -20,6 +20,7 @@ type Board = { createdBy: string modifiedBy: string type: BoardTypes + minimumRole: string title: string description: string @@ -37,6 +38,7 @@ type Board = { type BoardPatch = { type?: BoardTypes + minimumRole?: string title?: string description?: string icon?: string @@ -120,6 +122,7 @@ function createBoard(board?: Board): Board { createdBy: board?.createdBy || '', modifiedBy: board?.modifiedBy || '', type: board?.type || BoardTypePrivate, + minimumRole: board?.minimumRole || '', title: board?.title || '', description: board?.description || '', icon: board?.icon || '', diff --git a/webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.test.tsx b/webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.test.tsx index 9bf2be4a6..67d503099 100644 --- a/webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.test.tsx +++ b/webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.test.tsx @@ -67,6 +67,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => { description: 'test', showDescription: false, type: 'board', + minimumRole: 'editor', isTemplate: true, templateVersion: 0, icon: '🚴🏻‍♂️', @@ -84,6 +85,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => { updateAt: 20, deleteAt: 0, type: 'board', + minimumRole: 'editor', icon: '🚴🏻‍♂️', description: 'test', showDescription: false, diff --git a/webapp/src/components/shareBoard/teamPermissionsRow.tsx b/webapp/src/components/shareBoard/teamPermissionsRow.tsx index 60089b42c..eb440a25b 100644 --- a/webapp/src/components/shareBoard/teamPermissionsRow.tsx +++ b/webapp/src/components/shareBoard/teamPermissionsRow.tsx @@ -21,13 +21,14 @@ import BoardPermissionGate from '../permissions/boardPermissionGate' import mutator from '../../mutator' -function updateBoardType(board: Board, newType: string) { - if (board.type === newType) { +function updateBoardType(board: Board, newType: string, newMinimumRole: string) { + if (board.type === newType && board.minimumRole == newMinimumRole) { return } const newBoard = createBoard(board) newBoard.type = newType + newBoard.minimumRole = newMinimumRole mutator.updateBoard(newBoard, board, 'update board type') } @@ -37,7 +38,16 @@ const TeamPermissionsRow = (): JSX.Element => { const team = useAppSelector(getCurrentTeam) const board = useAppSelector(getCurrentBoard) - const currentRole = board.type === BoardTypeOpen ? 'Editor' : 'None' + let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'}) + if (board.type === BoardTypeOpen && board.minimumRole === 'admin') { + currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'}) + }else if (board.type === BoardTypeOpen && board.minimumRole === 'editor') { + currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'}) + }else if (board.type === BoardTypeOpen && board.minimumRole === 'commenter') { + currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'}) + }else if (board.type === BoardTypeOpen && board.minimumRole === 'viewer') { + currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'}) + } return (
@@ -54,26 +64,47 @@ const TeamPermissionsRow = (): JSX.Element => { + : null} + name={intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})} + onClick={() => updateBoardType(board, BoardTypeOpen, 'admin')} + /> : null} + check={board.minimumRole === '' || board.minimumRole === 'editor' } + icon={board.type === BoardTypeOpen && board.minimumRole === 'editor' ? : null} name={intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})} - onClick={() => updateBoardType(board, BoardTypeOpen)} + onClick={() => updateBoardType(board, BoardTypeOpen, 'editor')} + /> + : null} + name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})} + onClick={() => updateBoardType(board, BoardTypeOpen, 'commenter')} + /> + : null} + name={intl.formatMessage({id: 'BoardMember.schemeViwer', defaultMessage: 'Viewer'})} + onClick={() => updateBoardType(board, BoardTypeOpen, 'viewer')} /> : null} + icon={board.type === BoardTypePrivate ? : null} name={intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})} - onClick={() => updateBoardType(board, BoardTypePrivate)} + onClick={() => updateBoardType(board, BoardTypePrivate, 'editor')} /> @@ -82,7 +113,7 @@ const TeamPermissionsRow = (): JSX.Element => { permissions={[Permission.ManageBoardType]} invert={true} > - {currentRole} + {currentRoleName}