MM-22212 & MM-22208: Read from the higher-scoped scheme if the permission is non-moderated. (#13813)

* MM-22212: Read non-moderated permissions from higher-scoped scheme.

* MM-2212: Corrects test count in comment.

* MM-22212: Adds godoc comment.

* MM-2212: Switches to the channel roles check in a few more places.

* MM-22212: Refactors and fixes.

* MM-22212: Reverts change, no longer required.

* MM-22212: Removes translation.

* MM-22212: Un-comments merged new permission.

* MM-22212: Un-comments merged new permission.

* MM-22212: Performance tweak.

* MM-22212: Fixes some fmting.

* MM-22212: Add unit test for newly-added store methods.

* MM-22212: Renames app method.

* MM-22212: Re-uses existing function to find string in slice.

* MM-22212: Keeps 'higher-scoped' terminology for consistency.

* MM-22212: Refactors based on PR feedback.

* MM-22212: Fix for some bad merging.

* MM-22212: Renamed some things.

* MM-22212: Use an 'else' instead of a 'continue' for readability.

* MM-22212: Caches (*SqlRoleStore).ChannelRolesUnderTeamRole.

* MM-22212: Adds mock to new cache store.

* MM-22212: Adds missing open tracing app layer methods.

* MM-22212: Adds migration to add moderated permissions to channel_admin if present on channel_user.

* MM-22212: Migrates team schemes. Removes unused AppError.

* MM-22212: Fix for for if.

* MM-22212: Fixes iterator.

* MM-22212: Updates open tracing generated methods.

* MM-22212: Fix mocks.

* MM-22212: Change migration key name.

* MM-22212: Switched to data structure from other branch.

* MM-22212: Fixes tests after adding 'use_channel_mentions' to the channel_admin role.

* MM-22212: Adds tracking of channel moderation.

* Revert "MM-22212: Adds tracking of channel moderation."

This reverts commit 23689efa22.

* MM-22212: Switch some functions to methods and vice versa.

* MM-22212: Fix for refactor bug not notifiying websocket about changed role.

* MM-22212: Adds test for public/private 'manage_members' handling.

* MM-22122 Fix manage channel members edge case for public and private channels (#14049)

* MM-22212: Adds moderated permission to team_admin.

* MM-22212: Updates migration.

* MM-22212: Revert unnecessary update to default roles.

* Add channel scheme updated event when channel scheme is deleted or created (#14057)

* MM-22212: Adds newline.

* MM-22212: Migration fix.

* MM-22212: Fix for migration.

* MM-22212: Test fix.

Co-authored-by: Farhan Munshi <3207297+fm2munsh@users.noreply.github.com>
This commit is contained in:
Martin Kraft 2020-03-23 13:44:20 -04:00 committed by GitHub
parent ace46443b3
commit 4d99aa22ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1252 additions and 123 deletions

View file

@ -3110,6 +3110,34 @@ func TestGetChannelModerations(t *testing.T) {
}
}
})
t.Run("Retuns the correct value for manage_members depending on whether the channel is public or private", func(t *testing.T) {
scheme := th.SetupTeamScheme()
team.SchemeId = &scheme.Id
_, err := th.App.UpdateTeamScheme(team)
require.Nil(t, err)
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, scheme.DefaultChannelUserRole)
defer th.AddPermissionToRole(model.PERMISSION_CREATE_POST.Id, scheme.DefaultChannelUserRole)
// public channel does not have the permission
moderations, res := th.SystemAdminClient.GetChannelModerations(channel.Id, "")
require.Nil(t, res.Error)
for _, moderation := range moderations {
if moderation.Name == "manage_members" {
require.Equal(t, moderation.Roles.Members.Value, false)
}
}
// private channel does have the permission
moderations, res = th.SystemAdminClient.GetChannelModerations(th.BasicPrivateChannel.Id, "")
require.Nil(t, res.Error)
for _, moderation := range moderations {
if moderation.Name == "manage_members" {
require.Equal(t, moderation.Roles.Members.Value, true)
}
}
})
}
func TestPatchChannelModerations(t *testing.T) {

View file

@ -102,7 +102,7 @@ type AppIface interface {
// Notifies cluster peers through config change.
DisablePlugin(id string) *model.AppError
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
DoPermissionsMigrations() *model.AppError
DoPermissionsMigrations() error
// EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous
// activation if inactive anywhere in the cluster.
// Notifies cluster peers through config change.
@ -782,7 +782,7 @@ type AppIface interface {
SaveLicense(licenseBytes []byte) (*model.License, *model.AppError)
SaveReactionForPost(reaction *model.Reaction) (*model.Reaction, *model.AppError)
SaveUserTermsOfService(userId, termsOfServiceId string, accepted bool) *model.AppError
SchemesIterator(batchSize int) func() []*model.Scheme
SchemesIterator(scope string, batchSize int) func() []*model.Scheme
SearchArchivedChannels(teamId string, term string, userId string) (*model.ChannelList, *model.AppError)
SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError)
SearchChannelsForUser(userId, teamId, term string) (*model.ChannelList, *model.AppError)

View file

@ -596,6 +596,12 @@ func TestDoEmojisPermissionsMigration(t *testing.T) {
model.PERMISSION_DELETE_OTHERS_POSTS.Id,
model.PERMISSION_CREATE_EMOJIS.Id,
model.PERMISSION_DELETE_EMOJIS.Id,
model.PERMISSION_ADD_REACTION.Id,
model.PERMISSION_CREATE_POST.Id,
model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
model.PERMISSION_REMOVE_REACTION.Id,
model.PERMISSION_USE_CHANNEL_MENTIONS.Id,
}
sort.Strings(expected2)
sort.Strings(role2.Permissions)

View file

@ -6,7 +6,7 @@ package app
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v5/model"
)
@ -31,7 +31,13 @@ func TestCheckIfRolesGrantPermission(t *testing.T) {
}
for _, testcase := range cases {
assert.Equal(t, th.App.RolesGrantPermission(testcase.roles, testcase.permissionId), testcase.shouldGrant)
require.Equal(t, th.App.RolesGrantPermission(testcase.roles, testcase.permissionId), testcase.shouldGrant)
}
}
func TestChannelRolesGrantPermission(t *testing.T) {
testPermissionInheritance(t, func(t *testing.T, th *TestHelper, testData permissionInheritanceTestData) {
require.Equal(t, testData.shouldHavePermission, th.App.RolesGrantPermission([]string{testData.channelRole.Name}, testData.permission.Id), "row: %+v\n", testData.truthTableRow)
})
}

View file

@ -720,7 +720,7 @@ func (a *App) GetChannelModerationsForChannel(channel *model.Channel) ([]*model.
return nil, err
}
return buildChannelModerations(memberRole, guestRole, higherScopedMemberRole, higherScopedGuestRole), nil
return buildChannelModerations(channel.Type, memberRole, guestRole, higherScopedMemberRole, higherScopedGuestRole), nil
}
// PatchChannelModerationsForChannel Updates a channels scheme roles based on a given ChannelModerationPatch, if the permissions match the higher scoped role the scheme is deleted.
@ -736,8 +736,8 @@ func (a *App) PatchChannelModerationsForChannel(channel *model.Channel, channelM
return nil, err
}
higherScopedMemberPermissions := higherScopedMemberRole.GetChannelModeratedPermissions()
higherScopedGuestPermissions := higherScopedGuestRole.GetChannelModeratedPermissions()
higherScopedMemberPermissions := higherScopedMemberRole.GetChannelModeratedPermissions(channel.Type)
higherScopedGuestPermissions := higherScopedGuestRole.GetChannelModeratedPermissions(channel.Type)
for _, moderationPatch := range channelModerationsPatch {
if moderationPatch.Roles.Members != nil && *moderationPatch.Roles.Members && !higherScopedMemberPermissions[*moderationPatch.Name] {
@ -753,6 +753,9 @@ func (a *App) PatchChannelModerationsForChannel(channel *model.Channel, channelM
if _, err = a.CreateChannelScheme(channel); err != nil {
return nil, err
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_SCHEME_UPDATED, "", channel.Id, "", nil)
a.Publish(message)
mlog.Info("Permission scheme created.", mlog.String("channel_id", channel.Id), mlog.String("channel_name", channel.Name))
}
@ -796,6 +799,10 @@ func (a *App) PatchChannelModerationsForChannel(channel *model.Channel, channelM
if _, err = a.DeleteChannelScheme(channel); err != nil {
return nil, err
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_SCHEME_UPDATED, "", channel.Id, "", nil)
a.Publish(message)
memberRole = higherScopedMemberRole
guestRole = higherScopedGuestRole
mlog.Info("Permission scheme deleted.", mlog.String("channel_id", channel.Id), mlog.String("channel_name", channel.Name))
@ -810,14 +817,14 @@ func (a *App) PatchChannelModerationsForChannel(channel *model.Channel, channelM
}
}
return buildChannelModerations(memberRole, guestRole, higherScopedMemberRole, higherScopedGuestRole), nil
return buildChannelModerations(channel.Type, memberRole, guestRole, higherScopedMemberRole, higherScopedGuestRole), nil
}
func buildChannelModerations(memberRole *model.Role, guestRole *model.Role, higherScopedMemberRole *model.Role, higherScopedGuestRole *model.Role) []*model.ChannelModeration {
memberPermissions := memberRole.GetChannelModeratedPermissions()
guestPermissions := guestRole.GetChannelModeratedPermissions()
higherScopedMemberPermissions := higherScopedMemberRole.GetChannelModeratedPermissions()
higherScopedGuestPermissions := higherScopedGuestRole.GetChannelModeratedPermissions()
func buildChannelModerations(channelType string, memberRole *model.Role, guestRole *model.Role, higherScopedMemberRole *model.Role, higherScopedGuestRole *model.Role) []*model.ChannelModeration {
memberPermissions := memberRole.GetChannelModeratedPermissions(channelType)
guestPermissions := guestRole.GetChannelModeratedPermissions(channelType)
higherScopedMemberPermissions := higherScopedMemberRole.GetChannelModeratedPermissions(channelType)
higherScopedGuestPermissions := higherScopedGuestRole.GetChannelModeratedPermissions(channelType)
var channelModerations []*model.ChannelModeration
for _, permissionKey := range model.CHANNEL_MODERATED_PERMISSIONS {

View file

@ -905,7 +905,9 @@ func TestAllowChannelMentions(t *testing.T) {
t.Run("should return false for a post where the post user does not have USE_CHANNEL_MENTIONS permission", func(t *testing.T) {
defer th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
defer th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
allowChannelMentions := th.App.allowChannelMentions(post, 5)
assert.False(t, allowChannelMentions)
})

View file

@ -3046,7 +3046,7 @@ func (a *OpenTracingAppLayer) DoLogin(w http.ResponseWriter, r *http.Request, us
return resultVar0
}
func (a *OpenTracingAppLayer) DoPermissionsMigrations() *model.AppError {
func (a *OpenTracingAppLayer) DoPermissionsMigrations() error {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DoPermissionsMigrations")
@ -3060,11 +3060,6 @@ func (a *OpenTracingAppLayer) DoPermissionsMigrations() *model.AppError {
defer span.Finish()
resultVar0 := a.app.DoPermissionsMigrations()
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
@ -11547,7 +11542,7 @@ func (a *OpenTracingAppLayer) SaveUserTermsOfService(userId string, termsOfServi
return resultVar0
}
func (a *OpenTracingAppLayer) SchemesIterator(batchSize int) func() []*model.Scheme {
func (a *OpenTracingAppLayer) SchemesIterator(scope string, batchSize int) func() []*model.Scheme {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SchemesIterator")
@ -11559,7 +11554,7 @@ func (a *OpenTracingAppLayer) SchemesIterator(batchSize int) func() []*model.Sch
}()
defer span.Finish()
resultVar0 := a.app.SchemesIterator(batchSize)
resultVar0 := a.app.SchemesIterator(scope, batchSize)
return resultVar0
}

View file

@ -65,7 +65,7 @@ func (a *App) ResetPermissionsSystem() *model.AppError {
func (a *App) ExportPermissions(w io.Writer) error {
next := a.SchemesIterator(permissionsExportBatchSize)
next := a.SchemesIterator("", permissionsExportBatchSize)
var schemeBatch []*model.Scheme
for schemeBatch = next(); len(schemeBatch) > 0; schemeBatch = next() {

View file

@ -49,6 +49,10 @@ const (
PERMISSION_USE_CHANNEL_MENTIONS = "use_channel_mentions"
PERMISSION_CREATE_POST = "create_post"
PERMISSION_CREATE_POST_PUBLIC = "create_post_public"
PERMISSION_ADD_REACTION = "add_reaction"
PERMISSION_REMOVE_REACTION = "remove_reaction"
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = "manage_public_channel_members"
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = "manage_private_channel_members"
)
func isRole(role string) func(string, map[string]map[string]bool) bool {
@ -152,7 +156,7 @@ func (a *App) doPermissionsMigration(key string, migrationMap permissionsMap) *m
return nil
}
func getEmojisPermissionsSplitMigration() permissionsMap {
func (a *App) getEmojisPermissionsSplitMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: permissionExists(PERMISSION_MANAGE_EMOJIS),
@ -164,10 +168,10 @@ func getEmojisPermissionsSplitMigration() permissionsMap {
Add: []string{PERMISSION_DELETE_OTHERS_EMOJIS},
Remove: []string{PERMISSION_MANAGE_OTHERS_EMOJIS},
},
}
}, nil
}
func getWebhooksPermissionsSplitMigration() permissionsMap {
func (a *App) getWebhooksPermissionsSplitMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: permissionExists(PERMISSION_MANAGE_WEBHOOKS),
@ -179,10 +183,10 @@ func getWebhooksPermissionsSplitMigration() permissionsMap {
Add: []string{PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS, PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS},
Remove: []string{PERMISSION_MANAGE_OTHERS_WEBHOOKS},
},
}
}, nil
}
func getListJoinPublicPrivateTeamsPermissionsMigration() permissionsMap {
func (a *App) getListJoinPublicPrivateTeamsPermissionsMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
@ -194,29 +198,29 @@ func getListJoinPublicPrivateTeamsPermissionsMigration() permissionsMap {
Add: []string{PERMISSION_LIST_PUBLIC_TEAMS, PERMISSION_JOIN_PUBLIC_TEAMS},
Remove: []string{},
},
}
}, nil
}
func removePermanentDeleteUserMigration() permissionsMap {
func (a *App) removePermanentDeleteUserMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: permissionExists(PERMISSION_PERMANENT_DELETE_USER),
Remove: []string{PERMISSION_PERMANENT_DELETE_USER},
},
}
}, nil
}
func getAddBotPermissionsMigration() permissionsMap {
func (a *App) getAddBotPermissionsMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
Add: []string{PERMISSION_CREATE_BOT, PERMISSION_READ_BOTS, PERMISSION_READ_OTHERS_BOTS, PERMISSION_MANAGE_BOTS, PERMISSION_MANAGE_OTHERS_BOTS},
Remove: []string{},
},
}
}, nil
}
func applyChannelManageDeleteToChannelUser() permissionsMap {
func (a *App) applyChannelManageDeleteToChannelUser() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: permissionAnd(isRole(model.CHANNEL_USER_ROLE_ID), onOtherRole(model.TEAM_USER_ROLE_ID, permissionExists(PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES))),
@ -234,10 +238,10 @@ func applyChannelManageDeleteToChannelUser() permissionsMap {
On: permissionAnd(isRole(model.CHANNEL_USER_ROLE_ID), onOtherRole(model.TEAM_USER_ROLE_ID, permissionExists(PERMISSION_DELETE_PUBLIC_CHANNEL))),
Add: []string{PERMISSION_DELETE_PUBLIC_CHANNEL},
},
}
}, nil
}
func removeChannelManageDeleteFromTeamUser() permissionsMap {
func (a *App) removeChannelManageDeleteFromTeamUser() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: permissionAnd(isRole(model.TEAM_USER_ROLE_ID), permissionExists(PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES)),
@ -255,10 +259,10 @@ func removeChannelManageDeleteFromTeamUser() permissionsMap {
On: permissionAnd(isRole(model.TEAM_USER_ROLE_ID), permissionExists(PERMISSION_DELETE_PUBLIC_CHANNEL)),
Remove: []string{PERMISSION_DELETE_PUBLIC_CHANNEL},
},
}
}, nil
}
func getViewMembersPermissionMigration() permissionsMap {
func (a *App) getViewMembersPermissionMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: isRole(model.SYSTEM_USER_ROLE_ID),
@ -268,47 +272,154 @@ func getViewMembersPermissionMigration() permissionsMap {
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
Add: []string{PERMISSION_VIEW_MEMBERS},
},
}
}, nil
}
func getAddManageGuestsPermissionsMigration() permissionsMap {
func (a *App) getAddManageGuestsPermissionsMigration() (permissionsMap, error) {
return permissionsMap{
permissionTransformation{
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
Add: []string{PERMISSION_PROMOTE_GUEST, PERMISSION_DEMOTE_TO_GUEST, PERMISSION_INVITE_GUEST},
},
}
}, nil
}
func getAddUseMentionChannelsPermissionMigration() permissionsMap {
return permissionsMap{
permissionTransformation{
On: permissionOr(permissionExists(PERMISSION_CREATE_POST), permissionExists(PERMISSION_CREATE_POST_PUBLIC)),
Add: []string{PERMISSION_USE_CHANNEL_MENTIONS},
},
func (a *App) channelModerationPermissionsMigration() (permissionsMap, error) {
transformations := permissionsMap{}
var allTeamSchemes []*model.Scheme
next := a.SchemesIterator(model.SCHEME_SCOPE_TEAM, 100)
var schemeBatch []*model.Scheme
for schemeBatch = next(); len(schemeBatch) > 0; schemeBatch = next() {
allTeamSchemes = append(allTeamSchemes, schemeBatch...)
}
moderatedPermissionsMinusCreatePost := []string{
PERMISSION_ADD_REACTION,
PERMISSION_REMOVE_REACTION,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS,
PERMISSION_USE_CHANNEL_MENTIONS,
}
teamAndChannelAdminConditionalTransformations := func(teamAdminID, channelAdminID, channelUserID, channelGuestID string) []permissionTransformation {
transformations := []permissionTransformation{}
for _, perm := range moderatedPermissionsMinusCreatePost {
// add each moderated permission to the channel admin if channel user or guest has the permission
trans := permissionTransformation{
On: permissionAnd(
isRole(channelAdminID),
permissionOr(
onOtherRole(channelUserID, permissionExists(perm)),
onOtherRole(channelGuestID, permissionExists(perm)),
),
),
Add: []string{perm},
}
transformations = append(transformations, trans)
// add each moderated permission to the team admin if channel admin, user, or guest has the permission
trans = permissionTransformation{
On: permissionAnd(
isRole(teamAdminID),
permissionOr(
onOtherRole(channelAdminID, permissionExists(perm)),
onOtherRole(channelUserID, permissionExists(perm)),
onOtherRole(channelGuestID, permissionExists(perm)),
),
),
Add: []string{perm},
}
transformations = append(transformations, trans)
}
return transformations
}
for _, ts := range allTeamSchemes {
// ensure all team scheme channel admins have create_post because it's not exposed via the UI
trans := permissionTransformation{
On: isRole(ts.DefaultChannelAdminRole),
Add: []string{PERMISSION_CREATE_POST},
}
transformations = append(transformations, trans)
// ensure all team scheme team admins have create_post because it's not exposed via the UI
trans = permissionTransformation{
On: isRole(ts.DefaultTeamAdminRole),
Add: []string{PERMISSION_CREATE_POST},
}
transformations = append(transformations, trans)
// conditionally add all other moderated permissions to team and channel admins
transformations = append(transformations, teamAndChannelAdminConditionalTransformations(
ts.DefaultTeamAdminRole,
ts.DefaultChannelAdminRole,
ts.DefaultChannelUserRole,
ts.DefaultChannelGuestRole,
)...)
}
// ensure team admins have create_post
transformations = append(transformations, permissionTransformation{
On: isRole(model.TEAM_ADMIN_ROLE_ID),
Add: []string{PERMISSION_CREATE_POST},
})
// ensure channel admins have create_post
transformations = append(transformations, permissionTransformation{
On: isRole(model.CHANNEL_ADMIN_ROLE_ID),
Add: []string{PERMISSION_CREATE_POST},
})
// conditionally add all other moderated permissions to team and channel admins
transformations = append(transformations, teamAndChannelAdminConditionalTransformations(
model.TEAM_ADMIN_ROLE_ID,
model.CHANNEL_ADMIN_ROLE_ID,
model.CHANNEL_USER_ROLE_ID,
model.CHANNEL_GUEST_ROLE_ID,
)...)
// ensure system admin has all of the moderated permissions
transformations = append(transformations, permissionTransformation{
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
Add: append(moderatedPermissionsMinusCreatePost, PERMISSION_CREATE_POST),
})
// add the new use_channel_mentions permission to everyone who has create_post
transformations = append(transformations, permissionTransformation{
On: permissionOr(permissionExists(PERMISSION_CREATE_POST), permissionExists(PERMISSION_CREATE_POST_PUBLIC)),
Add: []string{PERMISSION_USE_CHANNEL_MENTIONS},
})
return transformations, nil
}
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
func (a *App) DoPermissionsMigrations() *model.AppError {
func (a *App) DoPermissionsMigrations() error {
PermissionsMigrations := []struct {
Key string
Migration func() permissionsMap
Migration func() (permissionsMap, error)
}{
{Key: model.MIGRATION_KEY_EMOJI_PERMISSIONS_SPLIT, Migration: getEmojisPermissionsSplitMigration},
{Key: model.MIGRATION_KEY_WEBHOOK_PERMISSIONS_SPLIT, Migration: getWebhooksPermissionsSplitMigration},
{Key: model.MIGRATION_KEY_LIST_JOIN_PUBLIC_PRIVATE_TEAMS, Migration: getListJoinPublicPrivateTeamsPermissionsMigration},
{Key: model.MIGRATION_KEY_REMOVE_PERMANENT_DELETE_USER, Migration: removePermanentDeleteUserMigration},
{Key: model.MIGRATION_KEY_ADD_BOT_PERMISSIONS, Migration: getAddBotPermissionsMigration},
{Key: model.MIGRATION_KEY_APPLY_CHANNEL_MANAGE_DELETE_TO_CHANNEL_USER, Migration: applyChannelManageDeleteToChannelUser},
{Key: model.MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER, Migration: removeChannelManageDeleteFromTeamUser},
{Key: model.MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION, Migration: getViewMembersPermissionMigration},
{Key: model.MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS, Migration: getAddManageGuestsPermissionsMigration},
{Key: model.MIGRATION_KEY_ADD_USE_CHANNEL_MENTIONS_PERMISSION, Migration: getAddUseMentionChannelsPermissionMigration},
{Key: model.MIGRATION_KEY_EMOJI_PERMISSIONS_SPLIT, Migration: a.getEmojisPermissionsSplitMigration},
{Key: model.MIGRATION_KEY_WEBHOOK_PERMISSIONS_SPLIT, Migration: a.getWebhooksPermissionsSplitMigration},
{Key: model.MIGRATION_KEY_LIST_JOIN_PUBLIC_PRIVATE_TEAMS, Migration: a.getListJoinPublicPrivateTeamsPermissionsMigration},
{Key: model.MIGRATION_KEY_REMOVE_PERMANENT_DELETE_USER, Migration: a.removePermanentDeleteUserMigration},
{Key: model.MIGRATION_KEY_ADD_BOT_PERMISSIONS, Migration: a.getAddBotPermissionsMigration},
{Key: model.MIGRATION_KEY_APPLY_CHANNEL_MANAGE_DELETE_TO_CHANNEL_USER, Migration: a.applyChannelManageDeleteToChannelUser},
{Key: model.MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER, Migration: a.removeChannelManageDeleteFromTeamUser},
{Key: model.MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION, Migration: a.getViewMembersPermissionMigration},
{Key: model.MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS, Migration: a.getAddManageGuestsPermissionsMigration},
{Key: model.MIGRATION_KEY_CHANNEL_MODERATIONS_PERMISSIONS, Migration: a.channelModerationPermissionsMigration},
}
for _, migration := range PermissionsMigrations {
if err := a.doPermissionsMigration(migration.Key, migration.Migration()); err != nil {
migMap, err := migration.Migration()
if err != nil {
return err
}
if err := a.doPermissionsMigration(migration.Key, migMap); err != nil {
return err
}
}

View file

@ -708,6 +708,7 @@ func TestCreatePost(t *testing.T) {
t.Run("Sets prop when post has mentions and user does not have USE_CHANNEL_MENTIONS", func(t *testing.T) {
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
postWithNoMention := &model.Post{
ChannelId: th.BasicChannel.Id,
@ -728,6 +729,7 @@ func TestCreatePost(t *testing.T) {
assert.Equal(t, rpost.GetProp(model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED), true)
th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
})
})
}
@ -798,6 +800,7 @@ func TestPatchPost(t *testing.T) {
t.Run("Sets prop when user does not have USE_CHANNEL_MENTIONS", func(t *testing.T) {
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
patchWithNoMention := &model.PostPatch{Message: model.NewString("This patch still does not have a mention")}
rpost, err = th.App.PatchPost(rpost.Id, patchWithNoMention)
@ -811,6 +814,7 @@ func TestPatchPost(t *testing.T) {
assert.Equal(t, rpost.GetProp(model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED), true)
th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_ADMIN_ROLE_ID)
})
})
}

View file

@ -9,6 +9,7 @@ import (
"strings"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/utils"
)
func (a *App) GetRole(id string) (*model.Role, *model.AppError) {
@ -20,11 +21,62 @@ func (a *App) GetAllRoles() ([]*model.Role, *model.AppError) {
}
func (a *App) GetRoleByName(name string) (*model.Role, *model.AppError) {
return a.Srv().Store.Role().GetByName(name)
role, err := a.Srv().Store.Role().GetByName(name)
if err != nil {
return nil, err
}
err = a.mergeChannelHigherScopedPermissions([]*model.Role{role})
if err != nil {
return nil, err
}
return role, nil
}
func (a *App) GetRolesByNames(names []string) ([]*model.Role, *model.AppError) {
return a.Srv().Store.Role().GetByNames(names)
roles, err := a.Srv().Store.Role().GetByNames(names)
if err != nil {
return nil, err
}
err = a.mergeChannelHigherScopedPermissions(roles)
if err != nil {
return nil, err
}
return roles, nil
}
// mergeChannelHigherScopedPermissions updates the permissions based on the role type, whether the permission is
// moderated, and the value of the permission on the higher-scoped scheme.
func (a *App) mergeChannelHigherScopedPermissions(roles []*model.Role) *model.AppError {
var higherScopeNamesToQuery []string
for _, role := range roles {
if role.SchemeManaged {
higherScopeNamesToQuery = append(higherScopeNamesToQuery, role.Name)
}
}
if len(higherScopeNamesToQuery) == 0 {
return nil
}
higherScopedPermissionsMap, err := a.Srv().Store.Role().ChannelHigherScopedPermissions(higherScopeNamesToQuery)
if err != nil {
return err
}
for _, role := range roles {
if role.SchemeManaged {
if higherScopedPermissions, ok := higherScopedPermissionsMap[role.Name]; ok {
role.MergeChannelHigherScopedPermissions(higherScopedPermissions)
}
}
}
return nil
}
func (a *App) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError) {
@ -59,10 +111,47 @@ func (a *App) UpdateRole(role *model.Role) (*model.Role, *model.AppError) {
if err != nil {
return nil, err
}
a.sendUpdatedRoleEvent(savedRole)
builtInChannelRoles := []string{
model.CHANNEL_GUEST_ROLE_ID,
model.CHANNEL_USER_ROLE_ID,
model.CHANNEL_ADMIN_ROLE_ID,
}
builtInRolesMinusChannelRoles := utils.RemoveStringsFromSlice(model.BuiltInSchemeManagedRoleIDs, builtInChannelRoles...)
if utils.StringInSlice(savedRole.Name, builtInRolesMinusChannelRoles) {
return savedRole, nil
}
var roleRetrievalFunc func() ([]*model.Role, *model.AppError)
if utils.StringInSlice(savedRole.Name, builtInChannelRoles) {
roleRetrievalFunc = func() ([]*model.Role, *model.AppError) {
return a.Srv().Store.Role().AllChannelSchemeRoles()
}
} else {
roleRetrievalFunc = func() ([]*model.Role, *model.AppError) {
return a.Srv().Store.Role().ChannelRolesUnderTeamRole(savedRole.Name)
}
}
impactedRoles, err := roleRetrievalFunc()
if err != nil {
return nil, err
}
impactedRoles = append(impactedRoles, role)
err = a.mergeChannelHigherScopedPermissions(impactedRoles)
if err != nil {
return nil, err
}
for _, ir := range impactedRoles {
a.sendUpdatedRoleEvent(ir)
}
return savedRole, nil
}
func (a *App) CheckRolesExist(roleNames []string) *model.AppError {

217
app/role_test.go Normal file
View file

@ -0,0 +1,217 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"encoding/csv"
"io/ioutil"
"os"
"strconv"
"strings"
"testing"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/stretchr/testify/require"
)
type permissionInheritanceTestData struct {
channelRole *model.Role
permission *model.Permission
shouldHavePermission bool
channel *model.Channel
higherScopedRole *model.Role
truthTableRow []string
}
func TestGetRolesByNames(t *testing.T) {
testPermissionInheritance(t, func(t *testing.T, th *TestHelper, testData permissionInheritanceTestData) {
actualRoles, err := th.App.GetRolesByNames([]string{testData.channelRole.Name})
require.Nil(t, err)
require.Len(t, actualRoles, 1)
actualRole := actualRoles[0]
require.NotNil(t, actualRole)
require.Equal(t, testData.channelRole.Name, actualRole.Name)
require.Equal(t, testData.shouldHavePermission, utils.StringInSlice(testData.permission.Id, actualRole.Permissions))
})
}
func TestGetRoleByName(t *testing.T) {
testPermissionInheritance(t, func(t *testing.T, th *TestHelper, testData permissionInheritanceTestData) {
actualRole, err := th.App.GetRoleByName(testData.channelRole.Name)
require.Nil(t, err)
require.NotNil(t, actualRole)
require.Equal(t, testData.channelRole.Name, actualRole.Name)
require.Equal(t, testData.shouldHavePermission, utils.StringInSlice(testData.permission.Id, actualRole.Permissions), "row: %+v", testData.truthTableRow)
})
}
// testPermissionInheritance tests 48 combinations of scheme, permission, role data.
func testPermissionInheritance(t *testing.T, testCallback func(t *testing.T, th *TestHelper, testData permissionInheritanceTestData)) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.SetLicense(model.NewTestLicense(""))
th.App.SetPhase2PermissionsMigrationStatus(true)
permissionsDefault := []string{
model.PERMISSION_MANAGE_CHANNEL_ROLES.Id,
model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
}
// Defer resetting the system scheme permissions
systemSchemeRoles, err := th.App.GetRolesByNames([]string{
model.CHANNEL_GUEST_ROLE_ID,
model.CHANNEL_USER_ROLE_ID,
model.CHANNEL_ADMIN_ROLE_ID,
})
require.Nil(t, err)
require.Len(t, systemSchemeRoles, 3)
// defer resetting the system role permissions
for _, systemRole := range systemSchemeRoles {
defer th.App.PatchRole(systemRole, &model.RolePatch{
Permissions: &systemRole.Permissions,
})
}
// Make a channel scheme, clear its permissions
channelScheme, err := th.App.CreateScheme(&model.Scheme{
Name: model.NewId(),
DisplayName: model.NewId(),
Scope: model.SCHEME_SCOPE_CHANNEL,
})
require.Nil(t, err)
defer th.App.DeleteScheme(channelScheme.Id)
team := th.CreateTeam()
defer th.App.PermanentDeleteTeamId(team.Id)
// Make a channel
channel := th.CreateChannel(team)
defer th.App.PermanentDeleteChannel(channel)
// Set the channel scheme
channel.SchemeId = &channelScheme.Id
channel, err = th.App.UpdateChannelScheme(channel)
require.Nil(t, err)
// Get the truth table from CSV
file, e := os.Open("tests/channel-role-has-permission.csv")
require.Nil(t, e)
defer file.Close()
b, e := ioutil.ReadAll(file)
require.Nil(t, e)
r := csv.NewReader(strings.NewReader(string(b)))
records, e := r.ReadAll()
require.Nil(t, e)
test := func(higherScopedGuest, higherScopedUser, higherScopedAdmin string) {
for _, roleNameUnderTest := range []string{higherScopedGuest, higherScopedUser, higherScopedAdmin} {
for i, row := range records {
// skip csv header
if i == 0 {
continue
}
higherSchemeHasPermission, e := strconv.ParseBool(row[0])
require.Nil(t, e)
permissionIsModerated, e := strconv.ParseBool(row[1])
require.Nil(t, e)
channelSchemeHasPermission, e := strconv.ParseBool(row[2])
require.Nil(t, e)
channelRoleIsChannelAdmin, e := strconv.ParseBool(row[3])
require.Nil(t, e)
shouldHavePermission, e := strconv.ParseBool(row[4])
require.Nil(t, e)
// skip some invalid combinations because of the outer loop iterating all 3 channel roles
if (channelRoleIsChannelAdmin && roleNameUnderTest != higherScopedAdmin) || (!channelRoleIsChannelAdmin && roleNameUnderTest == higherScopedAdmin) {
continue
}
// select the permission to test (moderated or non-moderated)
var permission *model.Permission
if permissionIsModerated {
permission = model.PERMISSION_CREATE_POST // moderated
} else {
permission = model.PERMISSION_READ_CHANNEL // non-moderated
}
// add or remove the permission from the higher-scoped scheme
higherScopedRole, testErr := th.App.GetRoleByName(roleNameUnderTest)
require.Nil(t, testErr)
var higherScopedPermissions []string
if higherSchemeHasPermission {
higherScopedPermissions = []string{permission.Id}
} else {
higherScopedPermissions = permissionsDefault
}
higherScopedRole, testErr = th.App.PatchRole(higherScopedRole, &model.RolePatch{Permissions: &higherScopedPermissions})
require.Nil(t, testErr)
// get channel role
var channelRoleName string
switch roleNameUnderTest {
case higherScopedGuest:
channelRoleName = channelScheme.DefaultChannelGuestRole
case higherScopedUser:
channelRoleName = channelScheme.DefaultChannelUserRole
case higherScopedAdmin:
channelRoleName = channelScheme.DefaultChannelAdminRole
}
channelRole, testErr := th.App.GetRoleByName(channelRoleName)
require.Nil(t, testErr)
// add or remove the permission from the channel scheme
var channelSchemePermissions []string
if channelSchemeHasPermission {
channelSchemePermissions = []string{permission.Id}
} else {
channelSchemePermissions = permissionsDefault
}
channelRole, testErr = th.App.PatchRole(channelRole, &model.RolePatch{Permissions: &channelSchemePermissions})
require.Nil(t, testErr)
testCallback(t, th, permissionInheritanceTestData{
channelRole: channelRole,
permission: permission,
shouldHavePermission: shouldHavePermission,
channel: channel,
higherScopedRole: higherScopedRole,
truthTableRow: row,
})
}
}
}
// test 24 combinations where the higher-scoped scheme is the SYSTEM scheme
test(model.CHANNEL_GUEST_ROLE_ID, model.CHANNEL_USER_ROLE_ID, model.CHANNEL_ADMIN_ROLE_ID)
// create a team scheme
teamScheme, err := th.App.CreateScheme(&model.Scheme{
Name: model.NewId(),
DisplayName: model.NewId(),
Scope: model.SCHEME_SCOPE_TEAM,
})
require.Nil(t, err)
defer th.App.DeleteScheme(teamScheme.Id)
// assign the scheme to the team
team.SchemeId = &teamScheme.Id
team, err = th.App.UpdateTeamScheme(team)
require.Nil(t, err)
// test 24 combinations where the higher-scoped scheme is a TEAM scheme
test(teamScheme.DefaultChannelGuestRole, teamScheme.DefaultChannelUserRole, teamScheme.DefaultChannelAdminRole)
}

View file

@ -139,10 +139,10 @@ func (a *App) IsPhase2MigrationCompleted() *model.AppError {
return nil
}
func (a *App) SchemesIterator(batchSize int) func() []*model.Scheme {
func (a *App) SchemesIterator(scope string, batchSize int) func() []*model.Scheme {
offset := 0
return func() []*model.Scheme {
schemes, err := a.Srv().Store.Scheme().GetAllPage("", offset, batchSize)
schemes, err := a.Srv().Store.Scheme().GetAllPage(scope, offset, batchSize)
if err != nil {
return []*model.Scheme{}
}

View file

@ -18,6 +18,7 @@ import (
"github.com/icrowley/fake"
"github.com/mattermost/mattermost-server/v5/app"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/spf13/cobra"
)
@ -52,15 +53,6 @@ func init() {
RootCmd.AddCommand(SampleDataCmd)
}
func sliceIncludes(vs []string, t string) bool {
for _, v := range vs {
if v == t {
return true
}
}
return false
}
func randomPastTime(seconds int) int64 {
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))
@ -343,7 +335,7 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
totalUsers := 3 + rand.Intn(3)
for len(users) < totalUsers {
user := allUsers[rand.Intn(len(allUsers))]
if !sliceIncludes(users, user) {
if !utils.StringInSlice(user, users) {
users = append(users, user)
}
}
@ -356,7 +348,7 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
totalUsers := 3 + rand.Intn(3)
for len(users) < totalUsers {
user := allUsers[rand.Intn(len(allUsers))]
if !sliceIncludes(users, user) {
if !utils.StringInSlice(user, users) {
users = append(users, user)
}
}

View file

@ -24,6 +24,7 @@ const (
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER_TEAMS = "inv_user_teams"
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLE_PERMISSIONS = "inv_role_permissions"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_BY_IDS = "inv_profile_ids"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_IN_CHANNEL = "inv_profile_in_channel"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes"

View file

@ -15,5 +15,5 @@ const (
MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER = "remove_channel_manage_delete_from_team_user"
MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION = "view_members_new_permission"
MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS = "add_manage_guests_permissions"
MIGRATION_KEY_ADD_USE_CHANNEL_MENTIONS_PERMISSION = "add_use_channel_mentions_permission"
MIGRATION_KEY_CHANNEL_MODERATIONS_PERMISSIONS = "channel_moderations_permissions"
)

View file

@ -9,6 +9,29 @@ import (
"strings"
)
var BuiltInSchemeManagedRoleIDs []string
func init() {
BuiltInSchemeManagedRoleIDs = []string{
SYSTEM_GUEST_ROLE_ID,
SYSTEM_USER_ROLE_ID,
SYSTEM_ADMIN_ROLE_ID,
SYSTEM_POST_ALL_ROLE_ID,
SYSTEM_POST_ALL_PUBLIC_ROLE_ID,
SYSTEM_USER_ACCESS_TOKEN_ROLE_ID,
TEAM_GUEST_ROLE_ID,
TEAM_USER_ROLE_ID,
TEAM_ADMIN_ROLE_ID,
TEAM_POST_ALL_ROLE_ID,
TEAM_POST_ALL_PUBLIC_ROLE_ID,
CHANNEL_GUEST_ROLE_ID,
CHANNEL_USER_ROLE_ID,
CHANNEL_ADMIN_ROLE_ID,
}
}
type RoleType string
type RoleScope string
@ -60,6 +83,11 @@ type RolePatch struct {
Permissions *[]string `json:"permissions"`
}
type RolePermissions struct {
RoleID string
Permissions []string
}
func (r *Role) ToJson() string {
b, _ := json.Marshal(r)
return string(b)
@ -99,6 +127,45 @@ func (r *Role) Patch(patch *RolePatch) {
}
}
// MergeChannelHigherScopedPermissions is meant to be invoked on a channel scheme's role and merges the higher-scoped
// channel role's permissions.
func (r *Role) MergeChannelHigherScopedPermissions(higherScopedPermissions *RolePermissions) {
mergedPermissions := []string{}
higherScopedPermissionsMap := AsStringBoolMap(higherScopedPermissions.Permissions)
rolePermissionsMap := AsStringBoolMap(r.Permissions)
for _, cp := range ALL_PERMISSIONS {
if cp.Scope != PERMISSION_SCOPE_CHANNEL {
continue
}
_, presentOnHigherScope := higherScopedPermissionsMap[cp.Id]
// For the channel admin role always look to the higher scope to determine if the role has ther permission.
// The channel admin is a special case because they're not part of the UI to be "channel moderated", only
// channel members and channel guests are.
if higherScopedPermissions.RoleID == CHANNEL_ADMIN_ROLE_ID && presentOnHigherScope {
mergedPermissions = append(mergedPermissions, cp.Id)
continue
}
_, permissionIsModerated := CHANNEL_MODERATED_PERMISSIONS_MAP[cp.Id]
if permissionIsModerated {
_, presentOnRole := rolePermissionsMap[cp.Id]
if presentOnRole && presentOnHigherScope {
mergedPermissions = append(mergedPermissions, cp.Id)
}
} else {
if presentOnHigherScope {
mergedPermissions = append(mergedPermissions, cp.Id)
}
}
}
r.Permissions = mergedPermissions
}
// Returns an array of permissions that are in either role.Permissions
// or patch.Permissions, but not both.
func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
@ -172,7 +239,7 @@ func ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []s
}
// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
func (r *Role) GetChannelModeratedPermissions() map[string]bool {
func (r *Role) GetChannelModeratedPermissions(channelType string) map[string]bool {
moderatedPermissions := make(map[string]bool)
for _, permission := range r.Permissions {
if _, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; !found {
@ -180,8 +247,20 @@ func (r *Role) GetChannelModeratedPermissions() map[string]bool {
}
for moderated, moderatedPermissionValue := range CHANNEL_MODERATED_PERMISSIONS_MAP {
// the moderated permission has already been found to be true so skip this iteration
if moderatedPermissions[moderatedPermissionValue] {
continue
}
if moderated == permission {
moderatedPermissions[moderatedPermissionValue] = true
// Special case where the channel moderated permission for `manage_members` is different depending on whether the channel is private or public
if moderated == PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id || moderated == PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id {
canManagePublic := channelType == CHANNEL_OPEN && moderated == PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id
canManagePrivate := channelType == CHANNEL_PRIVATE && moderated == PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
} else {
moderatedPermissions[moderatedPermissionValue] = true
}
}
}
}

View file

@ -231,16 +231,19 @@ func TestGetChannelModeratedPermissions(t *testing.T) {
tests := []struct {
Name string
Permissions []string
ChannelType string
Expected map[string]bool
}{
{
"Filters non moderated permissions",
[]string{PERMISSION_CREATE_BOT.Id},
CHANNEL_OPEN,
map[string]bool{},
},
{
"Returns a map of moderated permissions",
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_ADD_REACTION.Id, PERMISSION_REMOVE_REACTION.Id, PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, PERMISSION_USE_CHANNEL_MENTIONS.Id},
CHANNEL_OPEN,
map[string]bool{
CHANNEL_MODERATED_PERMISSIONS[0]: true,
CHANNEL_MODERATED_PERMISSIONS[1]: true,
@ -251,6 +254,7 @@ func TestGetChannelModeratedPermissions(t *testing.T) {
{
"Returns a map of moderated permissions when non moderated present",
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_CREATE_DIRECT_CHANNEL.Id},
CHANNEL_OPEN,
map[string]bool{
CHANNEL_MODERATED_PERMISSIONS[0]: true,
},
@ -258,13 +262,14 @@ func TestGetChannelModeratedPermissions(t *testing.T) {
{
"Returns a nothing when no permissions present",
[]string{},
CHANNEL_OPEN,
map[string]bool{},
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
role := &Role{Permissions: tc.Permissions}
moderatedPermissions := role.GetChannelModeratedPermissions()
moderatedPermissions := role.GetChannelModeratedPermissions(tc.ChannelType)
for permission := range moderatedPermissions {
assert.Equal(t, moderatedPermissions[permission], tc.Expected[permission])
}

View file

@ -639,3 +639,11 @@ func GetPreferredTimezone(timezone StringMap) string {
func IsSamlFile(saml *SamlSettings, filename string) bool {
return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile
}
func AsStringBoolMap(list []string) map[string]bool {
listMap := map[string]bool{}
for _, p := range list {
listMap[p] = true
}
return listMap
}

View file

@ -22,6 +22,7 @@ const (
WEBSOCKET_EVENT_CHANNEL_RESTORED = "channel_restored"
WEBSOCKET_EVENT_CHANNEL_UPDATED = "channel_updated"
WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED = "channel_member_updated"
WEBSOCKET_EVENT_CHANNEL_SCHEME_UPDATED = "channel_scheme_updated"
WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added"
WEBSOCKET_EVENT_GROUP_ADDED = "group_added"
WEBSOCKET_EVENT_NEW_USER = "new_user"

View file

@ -71,8 +71,9 @@ type LocalCacheStore struct {
fileInfo LocalCacheFileInfoStore
fileInfoCache cache.Cache
role LocalCacheRoleStore
roleCache cache.Cache
role LocalCacheRoleStore
roleCache cache.Cache
rolePermissionsCache cache.Cache
scheme LocalCacheSchemeStore
schemeCache cache.Cache
@ -118,6 +119,7 @@ func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterf
// Roles
localCacheStore.roleCache = cacheProvider.NewCacheWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES)
localCacheStore.rolePermissionsCache = cacheProvider.NewCacheWithParams(ROLE_CACHE_SIZE, "RolePermission", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLE_PERMISSIONS)
localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore}
// Schemes
@ -165,6 +167,7 @@ func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterf
if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, localCacheStore.role.handleClusterInvalidateRole)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLE_PERMISSIONS, localCacheStore.role.handleClusterInvalidateRolePermissions)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES, localCacheStore.scheme.handleClusterInvalidateScheme)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_FILE_INFOS, localCacheStore.fileInfo.handleClusterInvalidateFileInfo)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POST_TIME, localCacheStore.post.handleClusterInvalidateLastPostTime)
@ -294,4 +297,6 @@ func (s *LocalCacheStore) Invalidate() {
s.doClearCacheCluster(s.userProfileByIdsCache)
s.doClearCacheCluster(s.profilesInChannelCache)
s.doClearCacheCluster(s.teamAllTeamIdsForUserCache)
s.doClearCacheCluster(s.roleCache)
s.doClearCacheCluster(s.rolePermissionsCache)
}

View file

@ -32,6 +32,12 @@ func getMockCacheProvider() *mocks.CacheProvider {
mock.AnythingOfType("int64"),
mock.AnythingOfType("string")).Return(lru.New(128))
mockCacheProvider.On("NewCacheWithParams",
mock.AnythingOfType("int"),
"RolePermission",
mock.AnythingOfType("int64"),
mock.AnythingOfType("string")).Return(lru.New(128))
mockCacheProvider.On("NewCacheWithParams",
mock.AnythingOfType("int"),
"Scheme",

View file

@ -4,6 +4,9 @@
package localcachelayer
import (
"sort"
"strings"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
)
@ -21,9 +24,18 @@ func (s *LocalCacheRoleStore) handleClusterInvalidateRole(msg *model.ClusterMess
}
}
func (s *LocalCacheRoleStore) handleClusterInvalidateRolePermissions(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.rootStore.rolePermissionsCache.Purge()
} else {
s.rootStore.rolePermissionsCache.Remove(msg.Data)
}
}
func (s LocalCacheRoleStore) Save(role *model.Role) (*model.Role, *model.AppError) {
if len(role.Name) != 0 {
defer s.rootStore.doInvalidateCacheCluster(s.rootStore.roleCache, role.Name)
defer s.rootStore.doClearCacheCluster(s.rootStore.rolePermissionsCache) // TODO: This in other places?
}
return s.RoleStore.Save(role)
}
@ -67,6 +79,7 @@ func (s LocalCacheRoleStore) Delete(roleId string) (*model.Role, *model.AppError
if err == nil {
s.rootStore.doInvalidateCacheCluster(s.rootStore.roleCache, role.Name)
defer s.rootStore.doClearCacheCluster(s.rootStore.rolePermissionsCache)
}
return role, err
}
@ -74,6 +87,23 @@ func (s LocalCacheRoleStore) Delete(roleId string) (*model.Role, *model.AppError
func (s LocalCacheRoleStore) PermanentDeleteAll() *model.AppError {
defer s.rootStore.roleCache.Purge()
defer s.rootStore.doClearCacheCluster(s.rootStore.roleCache)
defer s.rootStore.doClearCacheCluster(s.rootStore.rolePermissionsCache)
return s.RoleStore.PermanentDeleteAll()
}
func (s LocalCacheRoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError) {
sort.Strings(roleNames)
cacheKey := strings.Join(roleNames, "/")
if rolePermissionsMap := s.rootStore.doStandardReadCache(s.rootStore.rolePermissionsCache, cacheKey); rolePermissionsMap != nil {
return rolePermissionsMap.(map[string]*model.RolePermissions), nil
}
rolePermissionsMap, err := s.RoleStore.ChannelHigherScopedPermissions(roleNames)
if err != nil {
return nil, err
}
s.rootStore.doStandardAddToCache(s.rootStore.rolePermissionsCache, cacheKey, rolePermissionsMap)
return rolePermissionsMap, nil
}

View file

@ -5179,7 +5179,61 @@ func (s *OpenTracingLayerReactionStore) Save(reaction *model.Reaction) (*model.R
return resultVar0, resultVar1
}
func (s *OpenTracingLayerRoleStore) Delete(roldId string) (*model.Role, *model.AppError) {
func (s *OpenTracingLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RoleStore.AllChannelSchemeRoles")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
resultVar0, resultVar1 := s.RoleStore.AllChannelSchemeRoles()
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (s *OpenTracingLayerRoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RoleStore.ChannelHigherScopedPermissions")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
resultVar0, resultVar1 := s.RoleStore.ChannelHigherScopedPermissions(roleNames)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (s *OpenTracingLayerRoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RoleStore.ChannelRolesUnderTeamRole")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
resultVar0, resultVar1 := s.RoleStore.ChannelRolesUnderTeamRole(roleName)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (s *OpenTracingLayerRoleStore) Delete(roleId string) (*model.Role, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RoleStore.Delete")
s.Root.Store.SetContext(newCtx)
@ -5188,7 +5242,7 @@ func (s *OpenTracingLayerRoleStore) Delete(roldId string) (*model.Role, *model.A
}()
defer span.Finish()
resultVar0, resultVar1 := s.RoleStore.Delete(roldId)
resultVar0, resultVar1 := s.RoleStore.Delete(roleId)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)

View file

@ -353,6 +353,7 @@ func (s SqlChannelStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_publicchannels_displayname_lower", "PublicChannels", "lower(DisplayName)")
}
s.CreateFullTextIndexIfNotExists("idx_publicchannels_search_txt", "PublicChannels", "Name, DisplayName, Purpose")
s.CreateIndexIfNotExists("idx_channels_scheme_id", "Channels", "SchemeId")
}
// MigratePublicChannels initializes the PublicChannels table with data created before this version

View file

@ -9,6 +9,7 @@ import (
"net/http"
"strings"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/gorp"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
@ -31,6 +32,15 @@ type Role struct {
BuiltIn bool
}
type channelRolesPermissions struct {
GuestRoleName string
UserRoleName string
AdminRoleName string
HigherScopedGuestPermissions string
HigherScopedUserPermissions string
HigherScopedAdminPermissions string
}
func NewRoleFromModel(role *model.Role) *Role {
permissionsMap := make(map[string]bool)
permissions := ""
@ -85,9 +95,6 @@ func newSqlRoleStore(sqlStore SqlStore) store.RoleStore {
return s
}
func (s SqlRoleStore) createIndexesIfNotExists() {
}
func (s *SqlRoleStore) Save(role *model.Role) (*model.Role, *model.AppError) {
// Check the role is valid before proceeding.
if !role.IsValidWithoutId() {
@ -240,3 +247,142 @@ func (s *SqlRoleStore) PermanentDeleteAll() *model.AppError {
return nil
}
func (s *SqlRoleStore) channelHigherScopedPermissionsQuery(roleNames []string) string {
sqlTmpl := `
SELECT
RoleSchemes.DefaultChannelGuestRole AS GuestRoleName,
RoleSchemes.DefaultChannelUserRole AS UserRoleName,
RoleSchemes.DefaultChannelAdminRole AS AdminRoleName,
GuestRoles.Permissions AS HigherScopedGuestPermissions,
UserRoles.Permissions AS HigherScopedUserPermissions,
AdminRoles.Permissions AS HigherScopedAdminPermissions
FROM
Schemes AS RoleSchemes
JOIN Channels ON Channels.SchemeId = RoleSchemes.Id
JOIN Teams ON Teams.Id = Channels.TeamId
JOIN Schemes ON Schemes.Id = Teams.SchemeId
JOIN Roles AS GuestRoles ON GuestRoles.Name = Schemes.DefaultChannelGuestRole
JOIN Roles AS UserRoles ON UserRoles.Name = Schemes.DefaultChannelUserRole
JOIN Roles AS AdminRoles ON AdminRoles.Name = Schemes.DefaultChannelAdminRole
WHERE
RoleSchemes.DefaultChannelGuestRole IN ('%[1]s')
OR RoleSchemes.DefaultChannelUserRole IN ('%[1]s')
OR RoleSchemes.DefaultChannelAdminRole IN ('%[1]s')
UNION
SELECT
Schemes.DefaultChannelGuestRole AS GuestRoleName,
Schemes.DefaultChannelUserRole AS UserRoleName,
Schemes.DefaultChannelAdminRole AS AdminRoleName,
GuestRoles.Permissions AS HigherScopedGuestPermissions,
UserRoles.Permissions AS HigherScopedUserPermissions,
AdminRoles.Permissions AS HigherScopedAdminPermissions
FROM
Schemes
JOIN Channels ON Channels.SchemeId = Schemes.Id
JOIN Teams ON Teams.Id = Channels.TeamId
JOIN Roles AS GuestRoles ON GuestRoles.Name = '%[2]s'
JOIN Roles AS UserRoles ON UserRoles.Name = '%[3]s'
JOIN Roles AS AdminRoles ON AdminRoles.Name = '%[4]s'
WHERE
(Schemes.DefaultChannelGuestRole IN ('%[1]s')
OR Schemes.DefaultChannelUserRole IN ('%[1]s')
OR Schemes.DefaultChannelAdminRole IN ('%[1]s'))
AND (Teams.SchemeId = ''
OR Teams.SchemeId IS NULL)
`
// The below three channel role names are referenced by their name value because there is no system scheme
// record that ships with Mattermost, otherwise the system scheme would be referenced by name and the channel
// roles would be referenced by their column names.
return fmt.Sprintf(
sqlTmpl,
strings.Join(roleNames, "', '"),
model.CHANNEL_GUEST_ROLE_ID,
model.CHANNEL_USER_ROLE_ID,
model.CHANNEL_ADMIN_ROLE_ID,
)
}
func (s *SqlRoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError) {
sql := s.channelHigherScopedPermissionsQuery(roleNames)
var rolesPermissions []*channelRolesPermissions
if _, err := s.GetReplica().Select(&rolesPermissions, sql); err != nil {
return nil, model.NewAppError("SqlRoleStore.HigherScopedPermissions", "store.sql_role.get_by_names.app_error", nil, err.Error(), http.StatusInternalServerError)
}
roleNameHigherScopedPermissions := map[string]*model.RolePermissions{}
for _, rp := range rolesPermissions {
roleNameHigherScopedPermissions[rp.GuestRoleName] = &model.RolePermissions{RoleID: model.CHANNEL_GUEST_ROLE_ID, Permissions: strings.Split(rp.HigherScopedGuestPermissions, " ")}
roleNameHigherScopedPermissions[rp.UserRoleName] = &model.RolePermissions{RoleID: model.CHANNEL_USER_ROLE_ID, Permissions: strings.Split(rp.HigherScopedUserPermissions, " ")}
roleNameHigherScopedPermissions[rp.AdminRoleName] = &model.RolePermissions{RoleID: model.CHANNEL_ADMIN_ROLE_ID, Permissions: strings.Split(rp.HigherScopedAdminPermissions, " ")}
}
return roleNameHigherScopedPermissions, nil
}
func (s *SqlRoleStore) AllChannelSchemeRoles() ([]*model.Role, *model.AppError) {
query := s.getQueryBuilder().
Select("Roles.*").
From("Schemes").
Join("Roles ON Schemes.DefaultChannelGuestRole = Roles.Name OR Schemes.DefaultChannelUserRole = Roles.Name OR Schemes.DefaultChannelAdminRole = Roles.Name").
Where(sq.Eq{"Schemes.Scope": model.SCHEME_SCOPE_CHANNEL}).
Where(sq.Eq{"Roles.DeleteAt": 0}).
Where(sq.Eq{"Schemes.DeleteAt": 0})
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlRoleStore.AllChannelSchemeManagedRoles", "store.sql.build_query.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var dbRoles []*Role
if _, err = s.GetReplica().Select(&dbRoles, queryString, args...); err != nil {
return nil, model.NewAppError("SqlRoleStore.AllChannelSchemeManagedRoles", "store.sql_role.get.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var roles []*model.Role
for _, dbRole := range dbRoles {
roles = append(roles, dbRole.ToModel())
}
return roles, nil
}
// ChannelRolesUnderTeamRole finds all of the channel-scheme roles under the team of the given team-scheme role.
func (s *SqlRoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, *model.AppError) {
query := s.getQueryBuilder().
Select("ChannelSchemeRoles.*").
From("Roles AS HigherScopedRoles").
Join("Schemes AS HigherScopedSchemes ON (HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelGuestRole OR HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelUserRole OR HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelAdminRole)").
Join("Teams ON Teams.SchemeId = HigherScopedSchemes.Id").
Join("Channels ON Channels.TeamId = Teams.Id").
Join("Schemes AS ChannelSchemes ON Channels.SchemeId = ChannelSchemes.Id").
Join("Roles AS ChannelSchemeRoles ON (ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelGuestRole OR ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelUserRole OR ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelAdminRole)").
Where(sq.Eq{"HigherScopedSchemes.Scope": model.SCHEME_SCOPE_TEAM}).
Where(sq.Eq{"HigherScopedRoles.Name": roleName}).
Where(sq.Eq{"HigherScopedRoles.DeleteAt": 0}).
Where(sq.Eq{"HigherScopedSchemes.DeleteAt": 0}).
Where(sq.Eq{"Teams.DeleteAt": 0}).
Where(sq.Eq{"Channels.DeleteAt": 0}).
Where(sq.Eq{"ChannelSchemes.DeleteAt": 0}).
Where(sq.Eq{"ChannelSchemeRoles.DeleteAt": 0})
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlRoleStore.ChannelRolesUnderTeamRole", "store.sql.build_query.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var dbRoles []*Role
if _, err = s.GetReplica().Select(&dbRoles, queryString, args...); err != nil {
return nil, model.NewAppError("SqlRoleStore.ChannelRolesUnderTeamRole", "store.sql_role.get.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var roles []*model.Role
for _, dbRole := range dbRoles {
roles = append(roles, dbRole.ToModel())
}
return roles, nil
}

View file

@ -41,6 +41,9 @@ func newSqlSchemeStore(sqlStore SqlStore) store.SchemeStore {
}
func (s SqlSchemeStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_schemes_channel_guest_role", "Schemes", "DefaultChannelGuestRole")
s.CreateIndexIfNotExists("idx_schemes_channel_user_role", "Schemes", "DefaultChannelUserRole")
s.CreateIndexIfNotExists("idx_schemes_channel_admin_role", "Schemes", "DefaultChannelAdminRole")
}
func (s *SqlSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, *model.AppError) {
@ -152,6 +155,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Tr
}
scheme.DefaultTeamGuestRole = savedRole.Name
}
if scheme.Scope == model.SCHEME_SCOPE_TEAM || scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
// Channel Admin Role
channelAdminRole := &model.Role{
@ -161,6 +165,10 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Tr
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelAdminRole.Permissions = []string{}
}
savedRole, err := s.SqlStore.Role().(*SqlRoleStore).createRole(channelAdminRole, transaction)
if err != nil {
return nil, err
@ -175,6 +183,10 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Tr
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelUserRole.Permissions = filterModerated(channelUserRole.Permissions)
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(channelUserRole, transaction)
if err != nil {
return nil, err
@ -189,6 +201,10 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Tr
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelGuestRole.Permissions = filterModerated(channelGuestRole.Permissions)
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(channelGuestRole, transaction)
if err != nil {
return nil, err
@ -215,6 +231,16 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Tr
return scheme, nil
}
func filterModerated(permissions []string) []string {
filteredPermissions := []string{}
for _, perm := range permissions {
if _, ok := model.CHANNEL_MODERATED_PERMISSIONS_MAP[perm]; ok {
filteredPermissions = append(filteredPermissions, perm)
}
}
return filteredPermissions
}
func (s *SqlSchemeStore) Get(schemeId string) (*model.Scheme, *model.AppError) {
var scheme model.Scheme
if err := s.GetReplica().SelectOne(&scheme, "SELECT * from Schemes WHERE Id = :Id", map[string]interface{}{"Id": schemeId}); err != nil {

View file

@ -205,10 +205,8 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.stores.TermsOfService.(SqlTermsOfServiceStore).createIndexesIfNotExists()
supplier.stores.UserTermsOfService.(SqlUserTermsOfServiceStore).createIndexesIfNotExists()
supplier.stores.linkMetadata.(*SqlLinkMetadataStore).createIndexesIfNotExists()
supplier.stores.reaction.(*SqlReactionStore).createIndexesIfNotExists()
supplier.stores.role.(*SqlRoleStore).createIndexesIfNotExists()
supplier.stores.scheme.(*SqlSchemeStore).createIndexesIfNotExists()
supplier.stores.group.(*SqlGroupStore).createIndexesIfNotExists()
supplier.stores.scheme.(*SqlSchemeStore).createIndexesIfNotExists()
supplier.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
return supplier

View file

@ -29,9 +29,6 @@ func newSqlReactionStore(sqlStore SqlStore) store.ReactionStore {
return s
}
func (s SqlReactionStore) createIndexesIfNotExists() {
}
func (s *SqlReactionStore) Save(reaction *model.Reaction) (*model.Reaction, *model.AppError) {
reaction.PreSave()
if err := reaction.IsValid(); err != nil {

View file

@ -183,6 +183,7 @@ func (s SqlTeamStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_teams_update_at", "Teams", "UpdateAt")
s.CreateIndexIfNotExists("idx_teams_create_at", "Teams", "CreateAt")
s.CreateIndexIfNotExists("idx_teams_delete_at", "Teams", "DeleteAt")
s.CreateIndexIfNotExists("idx_teams_scheme_id", "Teams", "SchemeId")
s.CreateIndexIfNotExists("idx_teammembers_team_id", "TeamMembers", "TeamId")
s.CreateIndexIfNotExists("idx_teammembers_user_id", "TeamMembers", "UserId")

View file

@ -772,8 +772,13 @@ func upgradeDatabaseToVersion521(sqlStore SqlStore) {
}
func upgradeDatabaseToVersion522(sqlStore SqlStore) {
// TODO: Uncomment following condition when version 5.22.0 is released
// if shouldPerformUpgrade(sqlStore, VERSION_5_21_0, VERSION_5_22_0) {
sqlStore.CreateIndexIfNotExists("idx_teams_scheme_id", "Teams", "SchemeId")
sqlStore.CreateIndexIfNotExists("idx_channels_scheme_id", "Channels", "SchemeId")
sqlStore.CreateIndexIfNotExists("idx_channels_scheme_id", "Channels", "SchemeId")
sqlStore.CreateIndexIfNotExists("idx_schemes_channel_guest_role", "Schemes", "DefaultChannelGuestRole")
sqlStore.CreateIndexIfNotExists("idx_schemes_channel_user_role", "Schemes", "DefaultChannelUserRole")
sqlStore.CreateIndexIfNotExists("idx_schemes_channel_admin_role", "Schemes", "DefaultChannelAdminRole")
// sqlStore.CreateColumnIfNotExistsNoDefault("Bots", "LastIconUpdate", "bigint", "bigint")
// sqlStore.AlterPrimaryKey("Reactions", []string{"PostId", "UserId", "EmojiName"})

View file

@ -569,8 +569,19 @@ type RoleStore interface {
GetAll() ([]*model.Role, *model.AppError)
GetByName(name string) (*model.Role, *model.AppError)
GetByNames(names []string) ([]*model.Role, *model.AppError)
Delete(roldId string) (*model.Role, *model.AppError)
Delete(roleId string) (*model.Role, *model.AppError)
PermanentDeleteAll() *model.AppError
// HigherScopedPermissions retrieves the higher-scoped permissions of a list of role names. The higher-scope
// (either team scheme or system scheme) is determined based on whether the team has a scheme or not.
ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError)
// AllChannelSchemeRoles returns all of the roles associated to channel schemes.
AllChannelSchemeRoles() ([]*model.Role, *model.AppError)
// ChannelRolesUnderTeamRole returns all of the non-deleted roles that are affected by updates to the
// given role.
ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, *model.AppError)
}
type SchemeStore interface {

View file

@ -16,6 +16,7 @@ import (
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
"github.com/mattermost/mattermost-server/v5/utils"
)
func TestGroupStore(t *testing.T, ss store.Store) {
@ -3620,15 +3621,6 @@ func groupTestpUpdateMembersRoleTeam(t *testing.T, ss store.Store) {
},
}
includes := func(list []string, item string) bool {
for _, it := range list {
if it == item {
return true
}
}
return false
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
err = ss.Team().UpdateMembersRole(team.Id, tt.inUserIDs)
@ -3639,7 +3631,7 @@ func groupTestpUpdateMembersRoleTeam(t *testing.T, ss store.Store) {
require.GreaterOrEqual(t, len(members), 4) // sanity check for team membership
for _, member := range members {
if includes(tt.inUserIDs, member.UserId) {
if utils.StringInSlice(member.UserId, tt.inUserIDs) {
require.True(t, member.SchemeAdmin)
} else {
require.False(t, member.SchemeAdmin)
@ -3738,15 +3730,6 @@ func groupTestpUpdateMembersRoleChannel(t *testing.T, ss store.Store) {
},
}
includes := func(list []string, item string) bool {
for _, it := range list {
if it == item {
return true
}
}
return false
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
err = ss.Channel().UpdateMembersRole(channel.Id, tt.inUserIDs)
@ -3758,7 +3741,7 @@ func groupTestpUpdateMembersRoleChannel(t *testing.T, ss store.Store) {
require.GreaterOrEqual(t, len(*members), 4) // sanity check for channel membership
for _, member := range *members {
if includes(tt.inUserIDs, member.UserId) {
if utils.StringInSlice(member.UserId, tt.inUserIDs) {
require.True(t, member.SchemeAdmin)
} else {
require.False(t, member.SchemeAdmin)

View file

@ -14,13 +14,88 @@ type RoleStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: roldId
func (_m *RoleStore) Delete(roldId string) (*model.Role, *model.AppError) {
ret := _m.Called(roldId)
// AllChannelSchemeRoles provides a mock function with given fields:
func (_m *RoleStore) AllChannelSchemeRoles() ([]*model.Role, *model.AppError) {
ret := _m.Called()
var r0 []*model.Role
if rf, ok := ret.Get(0).(func() []*model.Role); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func() *model.AppError); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// ChannelHigherScopedPermissions provides a mock function with given fields: roleNames
func (_m *RoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError) {
ret := _m.Called(roleNames)
var r0 map[string]*model.RolePermissions
if rf, ok := ret.Get(0).(func([]string) map[string]*model.RolePermissions); ok {
r0 = rf(roleNames)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]*model.RolePermissions)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func([]string) *model.AppError); ok {
r1 = rf(roleNames)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// ChannelRolesUnderTeamRole provides a mock function with given fields: roleName
func (_m *RoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, *model.AppError) {
ret := _m.Called(roleName)
var r0 []*model.Role
if rf, ok := ret.Get(0).(func(string) []*model.Role); ok {
r0 = rf(roleName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(roleName)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// Delete provides a mock function with given fields: roleId
func (_m *RoleStore) Delete(roleId string) (*model.Role, *model.AppError) {
ret := _m.Called(roleId)
var r0 *model.Role
if rf, ok := ret.Get(0).(func(string) *model.Role); ok {
r0 = rf(roldId)
r0 = rf(roleId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
@ -29,7 +104,7 @@ func (_m *RoleStore) Delete(roldId string) (*model.Role, *model.AppError) {
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(roldId)
r1 = rf(roleId)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)

View file

@ -21,6 +21,7 @@ func TestRoleStore(t *testing.T, ss store.Store) {
t.Run("GetNames", func(t *testing.T) { testRoleStoreGetByNames(t, ss) })
t.Run("Delete", func(t *testing.T) { testRoleStoreDelete(t, ss) })
t.Run("PermanentDeleteAll", func(t *testing.T) { testRoleStorePermanentDeleteAll(t, ss) })
t.Run("LowerScopedChannelSchemeRoles_AllChannelSchemeRoles", func(t *testing.T) { testRoleStoreLowerScopedChannelSchemeRoles(t, ss) })
}
func testRoleStoreSave(t *testing.T, ss store.Store) {
@ -356,3 +357,159 @@ func testRoleStorePermanentDeleteAll(t *testing.T, ss store.Store) {
assert.Nil(t, err)
assert.Empty(t, roles)
}
func testRoleStoreLowerScopedChannelSchemeRoles(t *testing.T, ss store.Store) {
createDefaultRoles(t, ss)
teamScheme1 := &model.Scheme{
DisplayName: model.NewId(),
Name: model.NewId(),
Description: model.NewId(),
Scope: model.SCHEME_SCOPE_TEAM,
}
teamScheme1, err := ss.Scheme().Save(teamScheme1)
require.Nil(t, err)
defer ss.Scheme().Delete(teamScheme1.Id)
teamScheme2 := &model.Scheme{
DisplayName: model.NewId(),
Name: model.NewId(),
Description: model.NewId(),
Scope: model.SCHEME_SCOPE_TEAM,
}
teamScheme2, err = ss.Scheme().Save(teamScheme2)
require.Nil(t, err)
defer ss.Scheme().Delete(teamScheme2.Id)
channelScheme1 := &model.Scheme{
DisplayName: model.NewId(),
Name: model.NewId(),
Description: model.NewId(),
Scope: model.SCHEME_SCOPE_CHANNEL,
}
channelScheme1, err = ss.Scheme().Save(channelScheme1)
require.Nil(t, err)
defer ss.Scheme().Delete(channelScheme1.Id)
channelScheme2 := &model.Scheme{
DisplayName: model.NewId(),
Name: model.NewId(),
Description: model.NewId(),
Scope: model.SCHEME_SCOPE_CHANNEL,
}
channelScheme2, err = ss.Scheme().Save(channelScheme2)
require.Nil(t, err)
defer ss.Scheme().Delete(channelScheme1.Id)
team1 := &model.Team{
DisplayName: "Name",
Name: "zz" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
SchemeId: &teamScheme1.Id,
}
team1, err = ss.Team().Save(team1)
require.Nil(t, err)
defer ss.Team().PermanentDelete(team1.Id)
team2 := &model.Team{
DisplayName: "Name",
Name: "zz" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
SchemeId: &teamScheme2.Id,
}
team2, err = ss.Team().Save(team2)
require.Nil(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.CHANNEL_OPEN,
SchemeId: &channelScheme1.Id,
}
channel1, err = ss.Channel().Save(channel1, -1)
require.Nil(t, err)
defer ss.Channel().Delete(channel1.Id, 0)
channel2 := &model.Channel{
TeamId: team2.Id,
DisplayName: "Display " + model.NewId(),
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
SchemeId: &channelScheme2.Id,
}
channel2, err = ss.Channel().Save(channel2, -1)
require.Nil(t, err)
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.Nil(t, err)
var actualRoleNames []string
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelGuestRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelGuestRole)
})
t.Run("user role for the right team's channels are returned", func(t *testing.T) {
actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelUserRole)
require.Nil(t, err)
var actualRoleNames []string
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelUserRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelUserRole)
})
t.Run("admin role for the right team's channels are returned", func(t *testing.T) {
actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelAdminRole)
require.Nil(t, err)
var actualRoleNames []string
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelAdminRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelAdminRole)
})
})
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.Nil(t, err)
var actualRoleNames []string
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
}
allRoleNames := []string{
channelScheme1.DefaultChannelGuestRole,
channelScheme2.DefaultChannelGuestRole,
channelScheme1.DefaultChannelUserRole,
channelScheme2.DefaultChannelUserRole,
channelScheme1.DefaultChannelAdminRole,
channelScheme2.DefaultChannelAdminRole,
}
for _, roleName := range allRoleNames {
require.Contains(t, actualRoleNames, roleName)
}
})
})
}

View file

@ -4716,10 +4716,58 @@ func (s *TimerLayerReactionStore) Save(reaction *model.Reaction) (*model.Reactio
return resultVar0, resultVar1
}
func (s *TimerLayerRoleStore) Delete(roldId string) (*model.Role, *model.AppError) {
func (s *TimerLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1 := s.RoleStore.Delete(roldId)
resultVar0, resultVar1 := s.RoleStore.AllChannelSchemeRoles()
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if resultVar1 == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("RoleStore.AllChannelSchemeRoles", success, elapsed)
}
return resultVar0, resultVar1
}
func (s *TimerLayerRoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1 := s.RoleStore.ChannelHigherScopedPermissions(roleNames)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if resultVar1 == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("RoleStore.ChannelHigherScopedPermissions", success, elapsed)
}
return resultVar0, resultVar1
}
func (s *TimerLayerRoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1 := s.RoleStore.ChannelRolesUnderTeamRole(roleName)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if resultVar1 == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("RoleStore.ChannelRolesUnderTeamRole", success, elapsed)
}
return resultVar0, resultVar1
}
func (s *TimerLayerRoleStore) Delete(roleId string) (*model.Role, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1 := s.RoleStore.Delete(roleId)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {

View file

@ -39,7 +39,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
systemStore.On("GetByName", model.MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER).Return(&model.System{Name: model.MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER, Value: "true"}, nil)
systemStore.On("GetByName", model.MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION).Return(&model.System{Name: model.MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION, Value: "true"}, nil)
systemStore.On("GetByName", model.MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS).Return(&model.System{Name: model.MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS, Value: "true"}, nil)
systemStore.On("GetByName", model.MIGRATION_KEY_ADD_USE_CHANNEL_MENTIONS_PERMISSION).Return(&model.System{Name: model.MIGRATION_KEY_ADD_USE_CHANNEL_MENTIONS_PERMISSION, Value: "true"}, nil)
systemStore.On("GetByName", model.MIGRATION_KEY_CHANNEL_MODERATIONS_PERMISSIONS).Return(&model.System{Name: model.MIGRATION_KEY_CHANNEL_MODERATIONS_PERMISSIONS, Value: "true"}, nil)
systemStore.On("Get").Return(make(model.StringMap), nil)
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
@ -57,6 +57,9 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
channelStore := mocks.ChannelStore{}
channelStore.On("ClearCaches").Return(nil)
schemeStore := mocks.SchemeStore{}
schemeStore.On("GetAllPage", model.SCHEME_SCOPE_TEAM, mock.Anything, 100).Return([]*model.Scheme{}, nil)
teamStore := mocks.TeamStore{}
mockStore.On("System").Return(&systemStore)
@ -65,6 +68,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
mockStore.On("Status").Return(&statusStore)
mockStore.On("Channel").Return(&channelStore)
mockStore.On("Team").Return(&teamStore)
mockStore.On("Scheme").Return(&schemeStore)
mockStore.On("Close").Return(nil)
mockStore.On("DropAllTables").Return(nil)
mockStore.On("MarkSystemRanUnitTests").Return(nil)

View file

@ -0,0 +1,17 @@
"let p be ""higher-scoped scheme has the permission""","let q be ""permission is moderated""","let r be ""channel scheme has the permission""","let s be ""channel role is channel_admin""","""channel role has the permission"" = p ∧ (s (q → r))"
TRUE,TRUE,TRUE,TRUE,TRUE
TRUE,TRUE,TRUE,FALSE,TRUE
TRUE,TRUE,FALSE,TRUE,TRUE
TRUE,TRUE,FALSE,FALSE,FALSE
TRUE,FALSE,TRUE,TRUE,TRUE
TRUE,FALSE,TRUE,FALSE,TRUE
TRUE,FALSE,FALSE,TRUE,TRUE
TRUE,FALSE,FALSE,FALSE,TRUE
FALSE,TRUE,TRUE,TRUE,FALSE
FALSE,TRUE,TRUE,FALSE,FALSE
FALSE,TRUE,FALSE,TRUE,FALSE
FALSE,TRUE,FALSE,FALSE,FALSE
FALSE,FALSE,TRUE,TRUE,FALSE
FALSE,FALSE,TRUE,FALSE,FALSE
FALSE,FALSE,FALSE,TRUE,FALSE
FALSE,FALSE,FALSE,FALSE,FALSE
1 let p be "higher-scoped scheme has the permission" let q be "permission is moderated" let r be "channel scheme has the permission" let s be "channel role is channel_admin" "channel role has the permission" = p ∧ (s ∨ (q → r))
2 TRUE TRUE TRUE TRUE TRUE
3 TRUE TRUE TRUE FALSE TRUE
4 TRUE TRUE FALSE TRUE TRUE
5 TRUE TRUE FALSE FALSE FALSE
6 TRUE FALSE TRUE TRUE TRUE
7 TRUE FALSE TRUE FALSE TRUE
8 TRUE FALSE FALSE TRUE TRUE
9 TRUE FALSE FALSE FALSE TRUE
10 FALSE TRUE TRUE TRUE FALSE
11 FALSE TRUE TRUE FALSE FALSE
12 FALSE TRUE FALSE TRUE FALSE
13 FALSE TRUE FALSE FALSE FALSE
14 FALSE FALSE TRUE TRUE FALSE
15 FALSE FALSE TRUE FALSE FALSE
16 FALSE FALSE FALSE TRUE FALSE
17 FALSE FALSE FALSE FALSE FALSE

View file

@ -19,6 +19,7 @@ func StringInSlice(a string, slice []string) bool {
return false
}
// RemoveStringFromSlice removes the first occurrence of a from slice.
func RemoveStringFromSlice(a string, slice []string) []string {
for i, str := range slice {
if str == a {
@ -28,6 +29,19 @@ func RemoveStringFromSlice(a string, slice []string) []string {
return slice
}
// RemoveStringsFromSlice removes all occurrences of strings from slice.
func RemoveStringsFromSlice(slice []string, strings ...string) []string {
newSlice := []string{}
for _, item := range slice {
if !StringInSlice(item, strings) {
newSlice = append(newSlice, item)
}
}
return newSlice
}
func StringArrayIntersection(arr1, arr2 []string) []string {
arrMap := map[string]bool{}
result := []string{}