mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-23 18:34:02 +02:00
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 <mattermod@users.noreply.github.com>
This commit is contained in:
parent
11bd3720f1
commit
d3edf2f698
@ -2759,7 +2759,7 @@ func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if patch.Type != nil {
|
if patch.Type != nil || patch.MinimumRole != nil {
|
||||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {
|
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.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"})
|
||||||
return
|
return
|
||||||
@ -3429,12 +3429,13 @@ func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently all memberships are created as editors by default
|
|
||||||
// TODO: Support different public roles
|
|
||||||
newBoardMember := &model.BoardMember{
|
newBoardMember := &model.BoardMember{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
BoardID: boardID,
|
BoardID: boardID,
|
||||||
SchemeEditor: true,
|
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)
|
auditRec := a.makeAuditRecord(r, "joinBoard", audit.Fail)
|
||||||
@ -3922,7 +3923,7 @@ func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if patch.Type != nil {
|
if patch.Type != nil || patch.MinimumRole != nil {
|
||||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {
|
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.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"})
|
||||||
return
|
return
|
||||||
|
@ -1945,7 +1945,83 @@ func TestJoinBoard(t *testing.T) {
|
|||||||
|
|
||||||
me := th.GetUser1()
|
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
|
teamID := testTeamID
|
||||||
newBoard := &model.Board{
|
newBoard := &model.Board{
|
||||||
Title: title,
|
Title: title,
|
||||||
@ -1969,6 +2045,10 @@ func TestJoinBoard(t *testing.T) {
|
|||||||
require.NotNil(t, member)
|
require.NotNil(t, member)
|
||||||
require.Equal(t, board.ID, member.BoardID)
|
require.Equal(t, board.ID, member.BoardID)
|
||||||
require.Equal(t, th.GetUser2().ID, member.UserID)
|
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")
|
s, _ := json.MarshalIndent(member, "", "\t")
|
||||||
t.Log(string(s))
|
t.Log(string(s))
|
||||||
|
@ -129,23 +129,27 @@ type TestData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupData(t *testing.T, th *TestHelper) TestData {
|
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)
|
require.NoError(t, err)
|
||||||
err = th.Server.App().InsertBlock(model.Block{ID: "block-1", Title: "Test", Type: "card", BoardID: customTemplate1.ID}, userAdminID)
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-1", Title: "Test", Type: "card", BoardID: customTemplate1.ID}, userAdminID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
customTemplate2, err := th.Server.App().CreateBoard(
|
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,
|
userAdminID,
|
||||||
true)
|
true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = th.Server.App().InsertBlock(model.Block{ID: "block-2", Title: "Test", Type: "card", BoardID: customTemplate2.ID}, userAdminID)
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-2", Title: "Test", Type: "card", BoardID: customTemplate2.ID}, userAdminID)
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
err = th.Server.App().InsertBlock(model.Block{ID: "block-3", Title: "Test", Type: "card", BoardID: board1.ID}, userAdminID)
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-3", Title: "Test", Type: "card", BoardID: board1.ID}, userAdminID)
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rBoard2, err := th.Server.App().GetBoard(board2.ID)
|
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) {
|
func TestPermissionsDeleteBoard(t *testing.T) {
|
||||||
ttCases := []TestCase{
|
ttCases := []TestCase{
|
||||||
{"/boards/{PRIVATE_BOARD_ID}", methodDelete, "", userAnon, http.StatusUnauthorized, 0},
|
{"/boards/{PRIVATE_BOARD_ID}", methodDelete, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
@ -3095,3 +3202,167 @@ func TestPermissionsBoardArchiveImport(t *testing.T) {
|
|||||||
runTestCases(t, ttCases, testData, clients)
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -7,12 +7,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BoardType string
|
type BoardType string
|
||||||
|
type BoardRole string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BoardTypeOpen BoardType = "O"
|
BoardTypeOpen BoardType = "O"
|
||||||
BoardTypePrivate BoardType = "P"
|
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
|
// Board groups a set of blocks and its layout
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type Board struct {
|
type Board struct {
|
||||||
@ -40,6 +49,10 @@ type Board struct {
|
|||||||
// required: true
|
// required: true
|
||||||
Type BoardType `json:"type"`
|
Type BoardType `json:"type"`
|
||||||
|
|
||||||
|
// The minimum role applied when somebody joins the board
|
||||||
|
// required: true
|
||||||
|
MinimumRole BoardRole `json:"minimumRole"`
|
||||||
|
|
||||||
// The title of the board
|
// The title of the board
|
||||||
// required: false
|
// required: false
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
@ -92,6 +105,10 @@ type BoardPatch struct {
|
|||||||
// required: false
|
// required: false
|
||||||
Type *BoardType `json:"type"`
|
Type *BoardType `json:"type"`
|
||||||
|
|
||||||
|
// The minimum role applied when somebody joins the board
|
||||||
|
// required: false
|
||||||
|
MinimumRole *BoardRole `json:"minimumRole"`
|
||||||
|
|
||||||
// The title of the board
|
// The title of the board
|
||||||
// required: false
|
// required: false
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title"`
|
||||||
@ -140,6 +157,10 @@ type BoardMember struct {
|
|||||||
// required: false
|
// required: false
|
||||||
Roles string `json:"roles"`
|
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
|
// Marks the user as an admin of the board
|
||||||
// required: true
|
// required: true
|
||||||
SchemeAdmin bool `json:"schemeAdmin"`
|
SchemeAdmin bool `json:"schemeAdmin"`
|
||||||
@ -221,6 +242,10 @@ func (p *BoardPatch) Patch(board *Board) *Board {
|
|||||||
board.Title = *p.Title
|
board.Title = *p.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.MinimumRole != nil {
|
||||||
|
board.MinimumRole = *p.MinimumRole
|
||||||
|
}
|
||||||
|
|
||||||
if p.Description != nil {
|
if p.Description != nil {
|
||||||
board.Description = *p.Description
|
board.Description = *p.Description
|
||||||
}
|
}
|
||||||
@ -296,11 +321,19 @@ func IsBoardTypeValid(t BoardType) bool {
|
|||||||
return t == BoardTypeOpen || t == BoardTypePrivate
|
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 {
|
func (p *BoardPatch) IsValid() error {
|
||||||
if p.Type != nil && !IsBoardTypeValid(*p.Type) {
|
if p.Type != nil && !IsBoardTypeValid(*p.Type) {
|
||||||
return InvalidBoardErr{"invalid-board-type"}
|
return InvalidBoardErr{"invalid-board-type"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.MinimumRole != nil && !IsBoardMinimumRoleValid(*p.MinimumRole) {
|
||||||
|
return InvalidBoardErr{"invalid-board-minimum-role"}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,6 +353,11 @@ func (b *Board) IsValid() error {
|
|||||||
if !IsBoardTypeValid(b.Type) {
|
if !IsBoardTypeValid(b.Type) {
|
||||||
return InvalidBoardErr{"invalid-board-type"}
|
return InvalidBoardErr{"invalid-board-type"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !IsBoardMinimumRoleValid(b.MinimumRole) {
|
||||||
|
return InvalidBoardErr{"invalid-board-minimum-role"}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,17 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod
|
|||||||
return false
|
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 {
|
switch permission {
|
||||||
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
||||||
return member.SchemeAdmin
|
return member.SchemeAdmin
|
||||||
|
@ -78,6 +78,17 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod
|
|||||||
return false
|
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 {
|
switch permission {
|
||||||
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
||||||
return member.SchemeAdmin
|
return member.SchemeAdmin
|
||||||
|
@ -31,6 +31,7 @@ func boardFields(prefix string) []string {
|
|||||||
"COALESCE(created_by, '')",
|
"COALESCE(created_by, '')",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"type",
|
"type",
|
||||||
|
"minimum_role",
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
"icon",
|
"icon",
|
||||||
@ -67,6 +68,7 @@ func boardHistoryFields() []string {
|
|||||||
"COALESCE(created_by, '')",
|
"COALESCE(created_by, '')",
|
||||||
"COALESCE(modified_by, '')",
|
"COALESCE(modified_by, '')",
|
||||||
"type",
|
"type",
|
||||||
|
"minimum_role",
|
||||||
"COALESCE(title, '')",
|
"COALESCE(title, '')",
|
||||||
"COALESCE(description, '')",
|
"COALESCE(description, '')",
|
||||||
"COALESCE(icon, '')",
|
"COALESCE(icon, '')",
|
||||||
@ -84,13 +86,14 @@ func boardHistoryFields() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var boardMemberFields = []string{
|
var boardMemberFields = []string{
|
||||||
"board_id",
|
"COALESCE(B.minimum_role, '')",
|
||||||
"user_id",
|
"BM.board_id",
|
||||||
"roles",
|
"BM.user_id",
|
||||||
"scheme_admin",
|
"BM.roles",
|
||||||
"scheme_editor",
|
"BM.scheme_admin",
|
||||||
"scheme_commenter",
|
"BM.scheme_editor",
|
||||||
"scheme_viewer",
|
"BM.scheme_commenter",
|
||||||
|
"BM.scheme_viewer",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) boardsFromRows(rows *sql.Rows) ([]*model.Board, error) {
|
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.CreatedBy,
|
||||||
&board.ModifiedBy,
|
&board.ModifiedBy,
|
||||||
&board.Type,
|
&board.Type,
|
||||||
|
&board.MinimumRole,
|
||||||
&board.Title,
|
&board.Title,
|
||||||
&board.Description,
|
&board.Description,
|
||||||
&board.Icon,
|
&board.Icon,
|
||||||
@ -149,6 +153,7 @@ func (s *SQLStore) boardMembersFromRows(rows *sql.Rows) ([]*model.BoardMember, e
|
|||||||
var boardMember model.BoardMember
|
var boardMember model.BoardMember
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
|
&boardMember.MinimumRole,
|
||||||
&boardMember.BoardID,
|
&boardMember.BoardID,
|
||||||
&boardMember.UserID,
|
&boardMember.UserID,
|
||||||
&boardMember.Roles,
|
&boardMember.Roles,
|
||||||
@ -308,6 +313,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
|
|||||||
"modified_by": userID,
|
"modified_by": userID,
|
||||||
"type": board.Type,
|
"type": board.Type,
|
||||||
"title": board.Title,
|
"title": board.Title,
|
||||||
|
"minimum_role": board.MinimumRole,
|
||||||
"description": board.Description,
|
"description": board.Description,
|
||||||
"icon": board.Icon,
|
"icon": board.Icon,
|
||||||
"show_description": board.ShowDescription,
|
"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}).
|
Where(sq.Eq{"id": board.ID}).
|
||||||
Set("modified_by", userID).
|
Set("modified_by", userID).
|
||||||
Set("type", board.Type).
|
Set("type", board.Type).
|
||||||
|
Set("minimum_role", board.MinimumRole).
|
||||||
Set("title", board.Title).
|
Set("title", board.Title).
|
||||||
Set("description", board.Description).
|
Set("description", board.Description).
|
||||||
Set("icon", board.Icon).
|
Set("icon", board.Icon).
|
||||||
@ -398,6 +405,7 @@ func (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error {
|
|||||||
"created_by": board.CreatedBy,
|
"created_by": board.CreatedBy,
|
||||||
"modified_by": userID,
|
"modified_by": userID,
|
||||||
"type": board.Type,
|
"type": board.Type,
|
||||||
|
"minimum_role": board.MinimumRole,
|
||||||
"title": board.Title,
|
"title": board.Title,
|
||||||
"description": board.Description,
|
"description": board.Description,
|
||||||
"icon": board.Icon,
|
"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) {
|
func (s *SQLStore) getMemberForBoard(db sq.BaseRunner, boardID, userID string) (*model.BoardMember, error) {
|
||||||
query := s.getQueryBuilder(db).
|
query := s.getQueryBuilder(db).
|
||||||
Select(boardMemberFields...).
|
Select(boardMemberFields...).
|
||||||
From(s.tablePrefix + "board_members").
|
From(s.tablePrefix + "board_members AS BM").
|
||||||
Where(sq.Eq{"board_id": boardID}).
|
LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id").
|
||||||
Where(sq.Eq{"user_id": userID})
|
Where(sq.Eq{"BM.board_id": boardID}).
|
||||||
|
Where(sq.Eq{"BM.user_id": userID})
|
||||||
|
|
||||||
rows, err := query.Query()
|
rows, err := query.Query()
|
||||||
if err != nil {
|
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) {
|
func (s *SQLStore) getMembersForUser(db sq.BaseRunner, userID string) ([]*model.BoardMember, error) {
|
||||||
query := s.getQueryBuilder(db).
|
query := s.getQueryBuilder(db).
|
||||||
Select(boardMemberFields...).
|
Select(boardMemberFields...).
|
||||||
From(s.tablePrefix + "board_members").
|
From(s.tablePrefix + "board_members AS BM").
|
||||||
Where(sq.Eq{"user_id": userID})
|
LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id").
|
||||||
|
Where(sq.Eq{"BM.user_id": userID})
|
||||||
|
|
||||||
rows, err := query.Query()
|
rows, err := query.Query()
|
||||||
if err != nil {
|
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) {
|
func (s *SQLStore) getMembersForBoard(db sq.BaseRunner, boardID string) ([]*model.BoardMember, error) {
|
||||||
query := s.getQueryBuilder(db).
|
query := s.getQueryBuilder(db).
|
||||||
Select(boardMemberFields...).
|
Select(boardMemberFields...).
|
||||||
From(s.tablePrefix + "board_members").
|
From(s.tablePrefix + "board_members AS BM").
|
||||||
Where(sq.Eq{"board_id": boardID})
|
LeftJoin(s.tablePrefix + "boards AS B ON B.id=BM.board_id").
|
||||||
|
Where(sq.Eq{"BM.board_id": boardID})
|
||||||
|
|
||||||
rows, err := query.Query()
|
rows, err := query.Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -711,6 +722,7 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st
|
|||||||
"modified_by",
|
"modified_by",
|
||||||
"type",
|
"type",
|
||||||
"title",
|
"title",
|
||||||
|
"minimum_role",
|
||||||
"description",
|
"description",
|
||||||
"icon",
|
"icon",
|
||||||
"show_description",
|
"show_description",
|
||||||
@ -730,6 +742,7 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st
|
|||||||
board.CreatedBy,
|
board.CreatedBy,
|
||||||
modifiedBy,
|
modifiedBy,
|
||||||
board.Type,
|
board.Type,
|
||||||
|
board.MinimumRole,
|
||||||
board.Title,
|
board.Title,
|
||||||
board.Description,
|
board.Description,
|
||||||
board.Icon,
|
board.Icon,
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE {{.prefix}}boards DROP COLUMN minimum_role;
|
||||||
|
ALTER TABLE {{.prefix}}boards_history DROP COLUMN minimum_role;
|
||||||
|
|
@ -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';
|
@ -20,6 +20,7 @@ type Board = {
|
|||||||
createdBy: string
|
createdBy: string
|
||||||
modifiedBy: string
|
modifiedBy: string
|
||||||
type: BoardTypes
|
type: BoardTypes
|
||||||
|
minimumRole: string
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
@ -37,6 +38,7 @@ type Board = {
|
|||||||
|
|
||||||
type BoardPatch = {
|
type BoardPatch = {
|
||||||
type?: BoardTypes
|
type?: BoardTypes
|
||||||
|
minimumRole?: string
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
icon?: string
|
icon?: string
|
||||||
@ -120,6 +122,7 @@ function createBoard(board?: Board): Board {
|
|||||||
createdBy: board?.createdBy || '',
|
createdBy: board?.createdBy || '',
|
||||||
modifiedBy: board?.modifiedBy || '',
|
modifiedBy: board?.modifiedBy || '',
|
||||||
type: board?.type || BoardTypePrivate,
|
type: board?.type || BoardTypePrivate,
|
||||||
|
minimumRole: board?.minimumRole || '',
|
||||||
title: board?.title || '',
|
title: board?.title || '',
|
||||||
description: board?.description || '',
|
description: board?.description || '',
|
||||||
icon: board?.icon || '',
|
icon: board?.icon || '',
|
||||||
|
@ -67,6 +67,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
|||||||
description: 'test',
|
description: 'test',
|
||||||
showDescription: false,
|
showDescription: false,
|
||||||
type: 'board',
|
type: 'board',
|
||||||
|
minimumRole: 'editor',
|
||||||
isTemplate: true,
|
isTemplate: true,
|
||||||
templateVersion: 0,
|
templateVersion: 0,
|
||||||
icon: '🚴🏻♂️',
|
icon: '🚴🏻♂️',
|
||||||
@ -84,6 +85,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
|||||||
updateAt: 20,
|
updateAt: 20,
|
||||||
deleteAt: 0,
|
deleteAt: 0,
|
||||||
type: 'board',
|
type: 'board',
|
||||||
|
minimumRole: 'editor',
|
||||||
icon: '🚴🏻♂️',
|
icon: '🚴🏻♂️',
|
||||||
description: 'test',
|
description: 'test',
|
||||||
showDescription: false,
|
showDescription: false,
|
||||||
|
@ -21,13 +21,14 @@ import BoardPermissionGate from '../permissions/boardPermissionGate'
|
|||||||
|
|
||||||
import mutator from '../../mutator'
|
import mutator from '../../mutator'
|
||||||
|
|
||||||
function updateBoardType(board: Board, newType: string) {
|
function updateBoardType(board: Board, newType: string, newMinimumRole: string) {
|
||||||
if (board.type === newType) {
|
if (board.type === newType && board.minimumRole == newMinimumRole) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBoard = createBoard(board)
|
const newBoard = createBoard(board)
|
||||||
newBoard.type = newType
|
newBoard.type = newType
|
||||||
|
newBoard.minimumRole = newMinimumRole
|
||||||
|
|
||||||
mutator.updateBoard(newBoard, board, 'update board type')
|
mutator.updateBoard(newBoard, board, 'update board type')
|
||||||
}
|
}
|
||||||
@ -37,7 +38,16 @@ const TeamPermissionsRow = (): JSX.Element => {
|
|||||||
const team = useAppSelector(getCurrentTeam)
|
const team = useAppSelector(getCurrentTeam)
|
||||||
const board = useAppSelector(getCurrentBoard)
|
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 (
|
return (
|
||||||
<div className='user-item'>
|
<div className='user-item'>
|
||||||
@ -54,26 +64,47 @@ const TeamPermissionsRow = (): JSX.Element => {
|
|||||||
<BoardPermissionGate permissions={[Permission.ManageBoardType]}>
|
<BoardPermissionGate permissions={[Permission.ManageBoardType]}>
|
||||||
<MenuWrapper>
|
<MenuWrapper>
|
||||||
<button className='user-item__button'>
|
<button className='user-item__button'>
|
||||||
{currentRole}
|
{currentRoleName}
|
||||||
<CompassIcon
|
<CompassIcon
|
||||||
icon='chevron-down'
|
icon='chevron-down'
|
||||||
className='CompassIcon'
|
className='CompassIcon'
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<Menu position='left'>
|
<Menu position='left'>
|
||||||
|
<Menu.Text
|
||||||
|
id='Admin'
|
||||||
|
check={board.minimumRole === 'admin'}
|
||||||
|
icon={board.type === BoardTypeOpen && board.minimumRole === 'admin' ? <CheckIcon/> : null}
|
||||||
|
name={intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})}
|
||||||
|
onClick={() => updateBoardType(board, BoardTypeOpen, 'admin')}
|
||||||
|
/>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='Editor'
|
id='Editor'
|
||||||
check={true}
|
check={board.minimumRole === '' || board.minimumRole === 'editor' }
|
||||||
icon={currentRole === 'Editor' ? <CheckIcon/> : null}
|
icon={board.type === BoardTypeOpen && board.minimumRole === 'editor' ? <CheckIcon/> : null}
|
||||||
name={intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})}
|
name={intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})}
|
||||||
onClick={() => updateBoardType(board, BoardTypeOpen)}
|
onClick={() => updateBoardType(board, BoardTypeOpen, 'editor')}
|
||||||
|
/>
|
||||||
|
<Menu.Text
|
||||||
|
id='Commenter'
|
||||||
|
check={board.minimumRole === 'commenter'}
|
||||||
|
icon={board.type === BoardTypeOpen && board.minimumRole === 'commenter' ? <CheckIcon/> : null}
|
||||||
|
name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}
|
||||||
|
onClick={() => updateBoardType(board, BoardTypeOpen, 'commenter')}
|
||||||
|
/>
|
||||||
|
<Menu.Text
|
||||||
|
id='Viewer'
|
||||||
|
check={board.minimumRole === 'viewer'}
|
||||||
|
icon={board.type === BoardTypeOpen && board.minimumRole === 'viewer' ? <CheckIcon/> : null}
|
||||||
|
name={intl.formatMessage({id: 'BoardMember.schemeViwer', defaultMessage: 'Viewer'})}
|
||||||
|
onClick={() => updateBoardType(board, BoardTypeOpen, 'viewer')}
|
||||||
/>
|
/>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='None'
|
id='None'
|
||||||
check={true}
|
check={true}
|
||||||
icon={currentRole === 'None' ? <CheckIcon/> : null}
|
icon={board.type === BoardTypePrivate ? <CheckIcon/> : null}
|
||||||
name={intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})}
|
name={intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})}
|
||||||
onClick={() => updateBoardType(board, BoardTypePrivate)}
|
onClick={() => updateBoardType(board, BoardTypePrivate, 'editor')}
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
@ -82,7 +113,7 @@ const TeamPermissionsRow = (): JSX.Element => {
|
|||||||
permissions={[Permission.ManageBoardType]}
|
permissions={[Permission.ManageBoardType]}
|
||||||
invert={true}
|
invert={true}
|
||||||
>
|
>
|
||||||
<span>{currentRole}</span>
|
<span>{currentRoleName}</span>
|
||||||
</BoardPermissionGate>
|
</BoardPermissionGate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user