// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package storetest import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/store" ) func TestRoleStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) { t.Run("Save", func(t *testing.T) { testRoleStoreSave(t, rctx, ss) }) t.Run("SavePreservingUnknownPermissions", func(t *testing.T) { testRoleStoreSavePreservingUnknownPermissions(t, rctx, ss) }) t.Run("Get", func(t *testing.T) { testRoleStoreGet(t, rctx, ss) }) t.Run("GetAll", func(t *testing.T) { testRoleStoreGetAll(t, rctx, ss) }) t.Run("GetByName", func(t *testing.T) { testRoleStoreGetByName(t, rctx, ss) }) t.Run("GetNames", func(t *testing.T) { testRoleStoreGetByNames(t, rctx, ss) }) t.Run("Delete", func(t *testing.T) { testRoleStoreDelete(t, rctx, ss) }) t.Run("PermanentDeleteAll", func(t *testing.T) { testRoleStorePermanentDeleteAll(t, rctx, ss) }) t.Run("LowerScopedChannelSchemeRoles_AllChannelSchemeRoles", func(t *testing.T) { testRoleStoreLowerScopedChannelSchemeRoles(t, rctx, ss) }) t.Run("ChannelHigherScopedPermissionsBlankTeamSchemeChannelGuest", func(t *testing.T) { testRoleStoreChannelHigherScopedPermissionsBlankTeamSchemeChannelGuest(t, rctx, ss, s) }) } func testRoleStoreSave(t *testing.T, rctx request.CTX, ss store.Store) { // Save a new role. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } d1, err := ss.Role().Save(r1) assert.NoError(t, err) assert.Len(t, d1.Id, 26) assert.Equal(t, r1.Name, d1.Name) assert.Equal(t, r1.DisplayName, d1.DisplayName) assert.Equal(t, r1.Description, d1.Description) assert.Equal(t, r1.Permissions, d1.Permissions) assert.Equal(t, r1.SchemeManaged, d1.SchemeManaged) // Change the role permissions and update. d1.Permissions = []string{ "invite_user", "add_user_to_team", "delete_public_channel", } d2, err := ss.Role().Save(d1) assert.NoError(t, err) assert.Len(t, d2.Id, 26) assert.Equal(t, r1.Name, d2.Name) assert.Equal(t, r1.DisplayName, d2.DisplayName) assert.Equal(t, r1.Description, d2.Description) assert.Equal(t, d1.Permissions, d2.Permissions) assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) // Try saving one with an invalid ID set. r3 := &model.Role{ Id: model.NewId(), Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } _, err = ss.Role().Save(r3) assert.Error(t, err) // Try saving one with a duplicate "name" field. r4 := &model.Role{ Name: r1.Name, DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } _, err = ss.Role().Save(r4) assert.Error(t, err) } func testRoleStoreSavePreservingUnknownPermissions(t *testing.T, _ request.CTX, ss store.Store) { // A role whose permissions are all valid for this build saves and round-trips // unchanged, just like Save. t.Run("preserves known permissions like Save", func(t *testing.T) { r := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{"invite_user", "add_user_to_team"}, SchemeManaged: false, } saved, err := ss.Role().SavePreservingUnknownPermissions(r) require.NoError(t, err) assert.Equal(t, r.Permissions, saved.Permissions) }) // The downgrade scenario from MM-68830: an existing role gains a permission this // build does not recognize (written by a newer release before the downgrade). The // migration re-saves every role; Save would reject the unknown permission, but // SavePreservingUnknownPermissions must keep it so it is not lost on a future upgrade. t.Run("tolerates and persists unknown permissions on update", func(t *testing.T) { unknown := "manage_own_agent_from_the_future" // Create the role as a known-good role first (the pre-downgrade state). existing, err := ss.Role().Save(&model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{"invite_user"}, }) require.NoError(t, err) // Simulate the newer release having added an unknown permission to the role. existing.Permissions = append(existing.Permissions, unknown) // Sanity check: the regular Save rejects the unknown permission. _, err = ss.Role().Save(existing) require.Error(t, err) saved, err := ss.Role().SavePreservingUnknownPermissions(existing) require.NoError(t, err) assert.Contains(t, saved.Permissions, unknown, "unknown permission should be preserved") // It must actually be persisted, not just returned. fetched, err := ss.Role().Get(saved.Id) require.NoError(t, err) assert.Contains(t, fetched.Permissions, unknown) }) // Tolerating unknown permissions must not mask genuine structural problems. t.Run("still rejects structurally invalid roles", func(t *testing.T) { r := &model.Role{ Name: "invalid-name", DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{"manage_own_agent_from_the_future"}, SchemeManaged: false, } _, err := ss.Role().SavePreservingUnknownPermissions(r) require.Error(t, err) var invErr *store.ErrInvalidInput require.ErrorAs(t, err, &invErr) }) } func testRoleStoreGetAll(t *testing.T, rctx request.CTX, ss store.Store) { prev, err := ss.Role().GetAll() require.NoError(t, err) prevCount := len(prev) // Save a role to test with. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } _, err = ss.Role().Save(r1) require.NoError(t, err) r2 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } _, err = ss.Role().Save(r2) require.NoError(t, err) data, err := ss.Role().GetAll() require.NoError(t, err) assert.Len(t, data, prevCount+2) } func testRoleStoreGet(t *testing.T, rctx request.CTX, ss store.Store) { // Save a role to test with. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } d1, err := ss.Role().Save(r1) assert.NoError(t, err) assert.Len(t, d1.Id, 26) // Get a valid role d2, err := ss.Role().Get(d1.Id) assert.NoError(t, err) assert.Equal(t, d1.Id, d2.Id) assert.Equal(t, r1.Name, d2.Name) assert.Equal(t, r1.DisplayName, d2.DisplayName) assert.Equal(t, r1.Description, d2.Description) assert.Equal(t, r1.Permissions, d2.Permissions) assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) // Get an invalid role _, err = ss.Role().Get(model.NewId()) assert.Error(t, err) } func testRoleStoreGetByName(t *testing.T, rctx request.CTX, ss store.Store) { // Save a role to test with. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } d1, err := ss.Role().Save(r1) assert.NoError(t, err) assert.Len(t, d1.Id, 26) // Get a valid role d2, err := ss.Role().GetByName(rctx, d1.Name) assert.NoError(t, err) assert.Equal(t, d1.Id, d2.Id) assert.Equal(t, r1.Name, d2.Name) assert.Equal(t, r1.DisplayName, d2.DisplayName) assert.Equal(t, r1.Description, d2.Description) assert.Equal(t, r1.Permissions, d2.Permissions) assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) // Get an invalid role _, err = ss.Role().GetByName(rctx, model.NewId()) assert.Error(t, err) } func testRoleStoreGetByNames(t *testing.T, rctx request.CTX, ss store.Store) { // Save some roles to test with. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } r2 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "read_channel", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } r3 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "delete_private_channel", "add_user_to_team", }, SchemeManaged: false, } d1, err := ss.Role().Save(r1) assert.NoError(t, err) assert.Len(t, d1.Id, 26) d2, err := ss.Role().Save(r2) assert.NoError(t, err) assert.Len(t, d2.Id, 26) d3, err := ss.Role().Save(r3) assert.NoError(t, err) assert.Len(t, d3.Id, 26) // Get two valid roles. n4 := []string{r1.Name, r2.Name} roles4, err := ss.Role().GetByNames(n4) assert.NoError(t, err) assert.Len(t, roles4, 2) assert.Contains(t, roles4, d1) assert.Contains(t, roles4, d2) assert.NotContains(t, roles4, d3) // Get two invalid roles. n5 := []string{model.NewId(), model.NewId()} roles5, err := ss.Role().GetByNames(n5) assert.NoError(t, err) assert.Empty(t, roles5) // Get one valid one and one invalid one. n6 := []string{r1.Name, model.NewId()} roles6, err := ss.Role().GetByNames(n6) assert.NoError(t, err) assert.Len(t, roles6, 1) assert.Contains(t, roles6, d1) assert.NotContains(t, roles6, d2) assert.NotContains(t, roles6, d3) } func testRoleStoreDelete(t *testing.T, rctx request.CTX, ss store.Store) { // Save a role to test with. r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } d1, err := ss.Role().Save(r1) assert.NoError(t, err) assert.Len(t, d1.Id, 26) // Check the role is there. _, err = ss.Role().Get(d1.Id) assert.NoError(t, err) // Delete the role. _, err = ss.Role().Delete(d1.Id) assert.NoError(t, err) // Check the role is deleted there. d2, err := ss.Role().Get(d1.Id) assert.NoError(t, err) assert.NotZero(t, d2.DeleteAt) d3, err := ss.Role().GetByName(rctx, d1.Name) assert.NoError(t, err) assert.NotZero(t, d3.DeleteAt) // Try and delete a role that does not exist. _, err = ss.Role().Delete(model.NewId()) assert.Error(t, err) } func testRoleStorePermanentDeleteAll(t *testing.T, rctx request.CTX, ss store.Store) { r1 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "invite_user", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } r2 := &model.Role{ Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), Permissions: []string{ "read_channel", "create_public_channel", "add_user_to_team", }, SchemeManaged: false, } _, err := ss.Role().Save(r1) require.NoError(t, err) _, err = ss.Role().Save(r2) require.NoError(t, err) roles, err := ss.Role().GetByNames([]string{r1.Name, r2.Name}) assert.NoError(t, err) assert.Len(t, roles, 2) err = ss.Role().PermanentDeleteAll() assert.NoError(t, err) roles, err = ss.Role().GetByNames([]string{r1.Name, r2.Name}) assert.NoError(t, err) assert.Empty(t, roles) } func testRoleStoreLowerScopedChannelSchemeRoles(t *testing.T, rctx request.CTX, ss store.Store) { createDefaultRoles(ss) teamScheme1 := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeTeam, } teamScheme1, err := ss.Scheme().Save(teamScheme1) require.NoError(t, err) defer ss.Scheme().Delete(teamScheme1.Id) teamScheme2 := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeTeam, } teamScheme2, err = ss.Scheme().Save(teamScheme2) require.NoError(t, err) defer ss.Scheme().Delete(teamScheme2.Id) channelScheme1 := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeChannel, } channelScheme1, err = ss.Scheme().Save(channelScheme1) require.NoError(t, err) defer ss.Scheme().Delete(channelScheme1.Id) channelScheme2 := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeChannel, } channelScheme2, err = ss.Scheme().Save(channelScheme2) require.NoError(t, err) defer ss.Scheme().Delete(channelScheme1.Id) team1 := &model.Team{ DisplayName: "Name", Name: "zz" + model.NewId(), Email: MakeEmail(), Type: model.TeamOpen, SchemeId: &teamScheme1.Id, } team1, err = ss.Team().Save(team1) require.NoError(t, err) defer ss.Team().PermanentDelete(team1.Id) team2 := &model.Team{ DisplayName: "Name", Name: "zz" + model.NewId(), Email: MakeEmail(), Type: model.TeamOpen, SchemeId: &teamScheme2.Id, } team2, err = ss.Team().Save(team2) require.NoError(t, err) defer ss.Team().PermanentDelete(team2.Id) channel1 := &model.Channel{ TeamId: team1.Id, DisplayName: "Display " + model.NewId(), Name: "zz" + model.NewId() + "b", Type: model.ChannelTypeOpen, SchemeId: &channelScheme1.Id, } channel1, nErr := ss.Channel().Save(rctx, channel1, -1) require.NoError(t, nErr) defer ss.Channel().Delete(channel1.Id, 0) channel2 := &model.Channel{ TeamId: team2.Id, DisplayName: "Display " + model.NewId(), Name: "zz" + model.NewId() + "b", Type: model.ChannelTypeOpen, SchemeId: &channelScheme2.Id, } channel2, nErr = ss.Channel().Save(rctx, channel2, -1) require.NoError(t, nErr) defer ss.Channel().Delete(channel2.Id, 0) t.Run("ChannelRolesUnderTeamRole", func(t *testing.T) { t.Run("guest role for the right team's channels are returned", func(t *testing.T) { actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelGuestRole) require.NoError(t, err) roleByName := make(map[string]*model.Role, len(actualRoles)) for _, role := range actualRoles { roleByName[role.Name] = role } require.Contains(t, roleByName, channelScheme1.DefaultChannelGuestRole) require.NotContains(t, roleByName, channelScheme2.DefaultChannelGuestRole) require.NotNil(t, roleByName[channelScheme1.DefaultChannelGuestRole].SchemeId) assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelGuestRole].SchemeId) }) t.Run("user role for the right team's channels are returned", func(t *testing.T) { actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelUserRole) require.NoError(t, err) roleByName := make(map[string]*model.Role, len(actualRoles)) for _, role := range actualRoles { roleByName[role.Name] = role } require.Contains(t, roleByName, channelScheme1.DefaultChannelUserRole) require.NotContains(t, roleByName, channelScheme2.DefaultChannelUserRole) require.NotNil(t, roleByName[channelScheme1.DefaultChannelUserRole].SchemeId) assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelUserRole].SchemeId) }) t.Run("admin role for the right team's channels are returned", func(t *testing.T) { actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelAdminRole) require.NoError(t, err) roleByName := make(map[string]*model.Role, len(actualRoles)) for _, role := range actualRoles { roleByName[role.Name] = role } require.Contains(t, roleByName, channelScheme1.DefaultChannelAdminRole) require.NotContains(t, roleByName, channelScheme2.DefaultChannelAdminRole) require.NotNil(t, roleByName[channelScheme1.DefaultChannelAdminRole].SchemeId) assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelAdminRole].SchemeId) }) }) t.Run("AllChannelSchemeRoles", func(t *testing.T) { t.Run("guest role for the right team's channels are returned", func(t *testing.T) { actualRoles, err := ss.Role().AllChannelSchemeRoles() require.NoError(t, err) roleByName := make(map[string]*model.Role, len(actualRoles)) for _, role := range actualRoles { roleByName[role.Name] = role } allRoleNames := []string{ channelScheme1.DefaultChannelGuestRole, channelScheme2.DefaultChannelGuestRole, channelScheme1.DefaultChannelUserRole, channelScheme2.DefaultChannelUserRole, channelScheme1.DefaultChannelAdminRole, channelScheme2.DefaultChannelAdminRole, } for _, roleName := range allRoleNames { require.Contains(t, roleByName, roleName) } // Roles for channelScheme1 must carry channelScheme1's ID. for _, roleName := range []string{ channelScheme1.DefaultChannelGuestRole, channelScheme1.DefaultChannelUserRole, channelScheme1.DefaultChannelAdminRole, } { require.NotNil(t, roleByName[roleName].SchemeId) assert.Equal(t, channelScheme1.Id, *roleByName[roleName].SchemeId) } // Roles for channelScheme2 must carry channelScheme2's ID. for _, roleName := range []string{ channelScheme2.DefaultChannelGuestRole, channelScheme2.DefaultChannelUserRole, channelScheme2.DefaultChannelAdminRole, } { require.NotNil(t, roleByName[roleName].SchemeId) assert.Equal(t, channelScheme2.Id, *roleByName[roleName].SchemeId) } }) }) } func testRoleStoreChannelHigherScopedPermissionsBlankTeamSchemeChannelGuest(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) { teamScheme := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeTeam, } teamScheme, err := ss.Scheme().Save(teamScheme) require.NoError(t, err) defer ss.Scheme().Delete(teamScheme.Id) channelScheme := &model.Scheme{ DisplayName: model.NewId(), Name: model.NewId(), Description: model.NewId(), Scope: model.SchemeScopeChannel, } channelScheme, err = ss.Scheme().Save(channelScheme) require.NoError(t, err) defer ss.Scheme().Delete(channelScheme.Id) team := &model.Team{ DisplayName: "Name", Name: "zz" + model.NewId(), Email: MakeEmail(), Type: model.TeamOpen, SchemeId: &teamScheme.Id, } team, err = ss.Team().Save(team) require.NoError(t, err) defer ss.Team().PermanentDelete(team.Id) channel := &model.Channel{ TeamId: team.Id, DisplayName: "Display " + model.NewId(), Name: "zz" + model.NewId() + "b", Type: model.ChannelTypeOpen, SchemeId: &channelScheme.Id, } channel, nErr := ss.Channel().Save(rctx, channel, -1) require.NoError(t, nErr) defer ss.Channel().Delete(channel.Id, 0) channelSchemeUserRole, err := ss.Role().GetByName(rctx, channelScheme.DefaultChannelUserRole) require.NoError(t, err) channelSchemeUserRole.Permissions = []string{} _, err = ss.Role().Save(channelSchemeUserRole) require.NoError(t, err) teamSchemeUserRole, err := ss.Role().GetByName(rctx, teamScheme.DefaultChannelUserRole) require.NoError(t, err) teamSchemeUserRole.Permissions = []string{model.PermissionUploadFile.Id} _, err = ss.Role().Save(teamSchemeUserRole) require.NoError(t, err) // get the channel scheme user role again and ensure that it has the permission inherited from the team // scheme user role roleMapBefore, err := ss.Role().ChannelHigherScopedPermissions([]string{channelSchemeUserRole.Name}) require.NoError(t, err) // blank-out the guest role to simulate an old team scheme, ensure it's blank result, sqlErr := s.GetMaster().Exec(fmt.Sprintf("UPDATE Schemes SET DefaultChannelGuestRole = '' WHERE Id = '%s'", teamScheme.Id)) require.NoError(t, sqlErr) rows, serr := result.RowsAffected() require.NoError(t, serr) require.Equal(t, int64(1), rows) teamScheme, err = ss.Scheme().Get(teamScheme.Id) require.NoError(t, err) require.Equal(t, "", teamScheme.DefaultChannelGuestRole) // trigger a cache clear _, err = ss.Role().Save(channelSchemeUserRole) require.NoError(t, err) roleMapAfter, err := ss.Role().ChannelHigherScopedPermissions([]string{channelSchemeUserRole.Name}) require.NoError(t, err) require.Equal(t, len(roleMapBefore), len(roleMapAfter)) }