mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-22206] Add PATCH channel moderations (PUT /moderations/patch) (#13845)
* MM-22205 Add get channel moderations endpoint
* MM-22206 Add patch channel moderations endpoint
Add api tests for patch channel moderations
* MM-22205 Ensure ordered permissions returned and create struct ChannelModeratedRoles
* MM-22206 Use structs instead of map
* MM-22206 Add test cases for GetChannelModeratedPermissions
* MM-22206 Add tests for ChannelModeratedPermissionsChangedByPatch
* MM-22206 Use NewBool instead of defining booleans
* MM-22206 Tie Channel Mentions to Create Posts when building Channel Moderations
* Revert "MM-22206 Tie Channel Mentions to Create Posts when building Channel Moderations"
This reverts commit a0bfc95f17.
* MM-22206 Review changes
Modify GetSchemeRolesForChannel to return named variables
Change calls to SessionHasPermissionToChannel to SessionHasPermissionTo
Add a CreateChannelScheme method
Add a DeleteChannelScheme method
Move GetChannelModeratedPermissions to Role model
* Fix lint
* Add ChannelModeration methods to App interface
* MM-22206 Rename method to GetTeamSchemeChannelRoles
* MM-22206 Check CHANNEL_MODERATED_PERMISSIONS_MAP for existing permission before iterating through it
* Modify wording to higherScoped to match #13813
* MM-22206 Delete channel scheme between tests
* MM-22206 Fix tests
* Actually patch role
* MM-22206 Shadow declaration of err
This commit is contained in:
@@ -84,6 +84,31 @@ type DirectChannelForExport struct {
|
||||
Members *[]string
|
||||
}
|
||||
|
||||
type ChannelModeration struct {
|
||||
Name string `json:"name"`
|
||||
Roles *ChannelModeratedRoles `json:"roles"`
|
||||
}
|
||||
|
||||
type ChannelModeratedRoles struct {
|
||||
Guests *ChannelModeratedRole `json:"guests"`
|
||||
Members *ChannelModeratedRole `json:"members"`
|
||||
}
|
||||
|
||||
type ChannelModeratedRole struct {
|
||||
Value bool `json:"value"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type ChannelModerationPatch struct {
|
||||
Name *string `json:"name"`
|
||||
Roles *ChannelModeratedRolesPatch `json:"roles"`
|
||||
}
|
||||
|
||||
type ChannelModeratedRolesPatch struct {
|
||||
Guests *bool `json:"guests"`
|
||||
Members *bool `json:"members"`
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
//
|
||||
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
|
||||
@@ -144,6 +169,18 @@ func ChannelPatchFromJson(data io.Reader) *ChannelPatch {
|
||||
return o
|
||||
}
|
||||
|
||||
func ChannelModerationsFromJson(data io.Reader) []*ChannelModeration {
|
||||
var o []*ChannelModeration
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
func ChannelModerationsPatchFromJson(data io.Reader) []*ChannelModerationPatch {
|
||||
var o []*ChannelModerationPatch
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Channel) Etag() string {
|
||||
return Etag(o.Id, o.UpdateAt)
|
||||
}
|
||||
|
||||
@@ -4912,3 +4912,22 @@ func (c *Client4) PatchConfig(config *Config) (*Config, *Response) {
|
||||
defer closeBody(r)
|
||||
return ConfigFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) GetChannelModerations(channelID string, etag string) ([]*ChannelModeration, *Response) {
|
||||
r, err := c.DoApiGet(c.GetChannelRoute(channelID)+"/moderations", etag)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return ChannelModerationsFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) PatchChannelModerations(channelID string, patch []*ChannelModerationPatch) ([]*ChannelModeration, *Response) {
|
||||
payload, _ := json.Marshal(patch)
|
||||
r, err := c.DoApiPut(c.GetChannelRoute(channelID)+"/moderations/patch", string(payload))
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return ChannelModerationsFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,9 @@ var PERMISSION_MANAGE_SYSTEM *Permission
|
||||
|
||||
var ALL_PERMISSIONS []*Permission
|
||||
|
||||
var CHANNEL_MODERATED_PERMISSIONS []string
|
||||
var CHANNEL_MODERATED_PERMISSIONS_MAP map[string]string
|
||||
|
||||
func initializePermissions() {
|
||||
PERMISSION_INVITE_USER = &Permission{
|
||||
"invite_user",
|
||||
@@ -641,6 +644,22 @@ func initializePermissions() {
|
||||
PERMISSION_DEMOTE_TO_GUEST,
|
||||
PERMISSION_USE_CHANNEL_MENTIONS,
|
||||
}
|
||||
|
||||
CHANNEL_MODERATED_PERMISSIONS = []string{
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
"create_reactions",
|
||||
"manage_members",
|
||||
PERMISSION_USE_CHANNEL_MENTIONS.Id,
|
||||
}
|
||||
|
||||
CHANNEL_MODERATED_PERMISSIONS_MAP = map[string]string{
|
||||
PERMISSION_CREATE_POST.Id: CHANNEL_MODERATED_PERMISSIONS[0],
|
||||
PERMISSION_ADD_REACTION.Id: CHANNEL_MODERATED_PERMISSIONS[1],
|
||||
PERMISSION_REMOVE_REACTION.Id: CHANNEL_MODERATED_PERMISSIONS[1],
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id: CHANNEL_MODERATED_PERMISSIONS[2],
|
||||
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id: CHANNEL_MODERATED_PERMISSIONS[2],
|
||||
PERMISSION_USE_CHANNEL_MENTIONS.Id: CHANNEL_MODERATED_PERMISSIONS[3],
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
109
model/role.go
109
model/role.go
@@ -123,6 +123,115 @@ func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
|
||||
var result []string
|
||||
|
||||
if patch.Permissions == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
roleMap := make(map[string]bool)
|
||||
patchMap := make(map[string]bool)
|
||||
|
||||
for _, permission := range role.Permissions {
|
||||
if channelModeratedPermissionName, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; found {
|
||||
roleMap[channelModeratedPermissionName] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, permission := range *patch.Permissions {
|
||||
if channelModeratedPermissionName, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; found {
|
||||
patchMap[channelModeratedPermissionName] = true
|
||||
}
|
||||
}
|
||||
|
||||
for permissionKey := range roleMap {
|
||||
if !patchMap[permissionKey] {
|
||||
result = append(result, permissionKey)
|
||||
}
|
||||
}
|
||||
|
||||
for permissionKey := range patchMap {
|
||||
if !roleMap[permissionKey] {
|
||||
result = append(result, permissionKey)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
|
||||
func (r *Role) GetChannelModeratedPermissions() map[string]bool {
|
||||
moderatedPermissions := make(map[string]bool)
|
||||
for _, permission := range r.Permissions {
|
||||
if _, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for moderated, moderatedPermissionValue := range CHANNEL_MODERATED_PERMISSIONS_MAP {
|
||||
if moderated == permission {
|
||||
moderatedPermissions[moderatedPermissionValue] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moderatedPermissions
|
||||
}
|
||||
|
||||
// RolePatchFromChannelModerationsPatch Creates and returns a RolePatch based on a slice of ChannelModerationPatchs, roleName is expected to be either "members" or "guests".
|
||||
func (r *Role) RolePatchFromChannelModerationsPatch(channelModerationsPatch []*ChannelModerationPatch, roleName string) *RolePatch {
|
||||
permissionsToAddToPatch := make(map[string]bool)
|
||||
|
||||
// Iterate through the list of existing permissions on the role and append permissions that we want to keep.
|
||||
for _, permission := range r.Permissions {
|
||||
// Permission is not moderated so dont add it to the patch and skip the channelModerationsPatch
|
||||
if _, isModerated := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; !isModerated {
|
||||
continue
|
||||
}
|
||||
|
||||
permissionEnabled := true
|
||||
// Check if permission has a matching moderated permission name inside the channel moderation patch
|
||||
for _, channelModerationPatch := range channelModerationsPatch {
|
||||
if *channelModerationPatch.Name == CHANNEL_MODERATED_PERMISSIONS_MAP[permission] {
|
||||
// Permission key exists in patch with a value of false so skip over it
|
||||
if roleName == "members" {
|
||||
if channelModerationPatch.Roles.Members != nil && !*channelModerationPatch.Roles.Members {
|
||||
permissionEnabled = false
|
||||
}
|
||||
} else if roleName == "guests" {
|
||||
if channelModerationPatch.Roles.Guests != nil && !*channelModerationPatch.Roles.Guests {
|
||||
permissionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if permissionEnabled {
|
||||
permissionsToAddToPatch[permission] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the patch and add any permissions that dont already exist on the role
|
||||
for _, channelModerationPatch := range channelModerationsPatch {
|
||||
for permission, moderatedPermissionName := range CHANNEL_MODERATED_PERMISSIONS_MAP {
|
||||
if roleName == "members" && channelModerationPatch.Roles.Members != nil && *channelModerationPatch.Roles.Members && *channelModerationPatch.Name == moderatedPermissionName {
|
||||
permissionsToAddToPatch[permission] = true
|
||||
}
|
||||
|
||||
if roleName == "guests" && channelModerationPatch.Roles.Guests != nil && *channelModerationPatch.Roles.Guests && *channelModerationPatch.Name == moderatedPermissionName {
|
||||
permissionsToAddToPatch[permission] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patchPermissions := make([]string, 0, len(permissionsToAddToPatch))
|
||||
for permission := range permissionsToAddToPatch {
|
||||
patchPermissions = append(patchPermissions, permission)
|
||||
}
|
||||
|
||||
return &RolePatch{Permissions: &patchPermissions}
|
||||
}
|
||||
|
||||
func (r *Role) IsValid() bool {
|
||||
if len(r.Id) != 26 {
|
||||
return false
|
||||
|
||||
273
model/role_test.go
Normal file
273
model/role_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChannelModeratedPermissionsChangedByPatch(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Permissions []string
|
||||
PatchPermissions []string
|
||||
Expected []string
|
||||
}{
|
||||
{
|
||||
"Empty patch returns empty slice",
|
||||
[]string{},
|
||||
[]string{},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"Adds permissions to empty initial permissions list",
|
||||
[]string{},
|
||||
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_ADD_REACTION.Id},
|
||||
[]string{CHANNEL_MODERATED_PERMISSIONS[0], CHANNEL_MODERATED_PERMISSIONS[1]},
|
||||
},
|
||||
{
|
||||
"Ignores non moderated permissions in initial permissions list",
|
||||
[]string{PERMISSION_ASSIGN_BOT.Id},
|
||||
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_REMOVE_REACTION.Id},
|
||||
[]string{CHANNEL_MODERATED_PERMISSIONS[0], CHANNEL_MODERATED_PERMISSIONS[1]},
|
||||
},
|
||||
{
|
||||
"Adds removed moderated permissions from initial permissions list",
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
[]string{},
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
},
|
||||
{
|
||||
"No changes returns empty slice",
|
||||
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_ASSIGN_BOT.Id},
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
baseRole := &Role{Permissions: tc.Permissions}
|
||||
rolePatch := &RolePatch{Permissions: &tc.PatchPermissions}
|
||||
result := ChannelModeratedPermissionsChangedByPatch(baseRole, rolePatch)
|
||||
assert.ElementsMatch(t, tc.Expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRolePatchFromChannelModerationsPatch(t *testing.T) {
|
||||
createPosts := CHANNEL_MODERATED_PERMISSIONS[0]
|
||||
createReactions := CHANNEL_MODERATED_PERMISSIONS[1]
|
||||
manageMembers := CHANNEL_MODERATED_PERMISSIONS[2]
|
||||
channelMentions := CHANNEL_MODERATED_PERMISSIONS[3]
|
||||
|
||||
basePermissions := []string{
|
||||
PERMISSION_ADD_REACTION.Id,
|
||||
PERMISSION_REMOVE_REACTION.Id,
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
PERMISSION_USE_CHANNEL_MENTIONS.Id,
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
|
||||
PERMISSION_UPLOAD_FILE.Id,
|
||||
PERMISSION_GET_PUBLIC_LINK.Id,
|
||||
PERMISSION_USE_SLASH_COMMANDS.Id,
|
||||
}
|
||||
|
||||
baseModeratedPermissions := []string{
|
||||
PERMISSION_ADD_REACTION.Id,
|
||||
PERMISSION_REMOVE_REACTION.Id,
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
|
||||
PERMISSION_USE_CHANNEL_MENTIONS.Id,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Permissions []string
|
||||
ChannelModerationsPatch []*ChannelModerationPatch
|
||||
RoleName string
|
||||
ExpectedPatchPermissions []string
|
||||
}{
|
||||
{
|
||||
"Patch to member role adding a permission that already exists",
|
||||
basePermissions,
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
baseModeratedPermissions,
|
||||
},
|
||||
{
|
||||
"Patch to member role with moderation patch for guest role",
|
||||
basePermissions,
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Guests: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
baseModeratedPermissions,
|
||||
},
|
||||
{
|
||||
"Patch to guest role with moderation patch for member role",
|
||||
basePermissions,
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"guests",
|
||||
baseModeratedPermissions,
|
||||
},
|
||||
{
|
||||
"Patch to member role removing multiple channel moderated permissions",
|
||||
basePermissions,
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &manageMembers,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &channelMentions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
},
|
||||
{
|
||||
"Patch to guest role removing multiple channel moderated permissions",
|
||||
basePermissions,
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Guests: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &manageMembers,
|
||||
Roles: &ChannelModeratedRolesPatch{Guests: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &channelMentions,
|
||||
Roles: &ChannelModeratedRolesPatch{Guests: NewBool(false)},
|
||||
},
|
||||
},
|
||||
"guests",
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
},
|
||||
{
|
||||
"Patch enabling and removing multiple channel moderated permissions ",
|
||||
[]string{PERMISSION_ADD_REACTION.Id, PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id},
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &manageMembers,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &channelMentions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
{
|
||||
Name: &createPosts,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_USE_CHANNEL_MENTIONS.Id},
|
||||
},
|
||||
{
|
||||
"Patch enabling a partially enabled permission",
|
||||
[]string{PERMISSION_ADD_REACTION.Id},
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
[]string{PERMISSION_ADD_REACTION.Id, PERMISSION_REMOVE_REACTION.Id},
|
||||
},
|
||||
{
|
||||
"Patch disabling a partially disabled permission",
|
||||
[]string{PERMISSION_ADD_REACTION.Id},
|
||||
[]*ChannelModerationPatch{
|
||||
{
|
||||
Name: &createReactions,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(false)},
|
||||
},
|
||||
{
|
||||
Name: &createPosts,
|
||||
Roles: &ChannelModeratedRolesPatch{Members: NewBool(true)},
|
||||
},
|
||||
},
|
||||
"members",
|
||||
[]string{PERMISSION_CREATE_POST.Id},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
baseRole := &Role{Permissions: tc.Permissions}
|
||||
rolePatch := baseRole.RolePatchFromChannelModerationsPatch(tc.ChannelModerationsPatch, tc.RoleName)
|
||||
assert.ElementsMatch(t, tc.ExpectedPatchPermissions, *rolePatch.Permissions)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannelModeratedPermissions(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Permissions []string
|
||||
Expected map[string]bool
|
||||
}{
|
||||
{
|
||||
"Filters non moderated permissions",
|
||||
[]string{PERMISSION_CREATE_BOT.Id},
|
||||
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},
|
||||
map[string]bool{
|
||||
CHANNEL_MODERATED_PERMISSIONS[0]: true,
|
||||
CHANNEL_MODERATED_PERMISSIONS[1]: true,
|
||||
CHANNEL_MODERATED_PERMISSIONS[2]: true,
|
||||
CHANNEL_MODERATED_PERMISSIONS[3]: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"Returns a map of moderated permissions when non moderated present",
|
||||
[]string{PERMISSION_CREATE_POST.Id, PERMISSION_CREATE_DIRECT_CHANNEL.Id},
|
||||
map[string]bool{
|
||||
CHANNEL_MODERATED_PERMISSIONS[0]: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"Returns a nothing when no permissions present",
|
||||
[]string{},
|
||||
map[string]bool{},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
role := &Role{Permissions: tc.Permissions}
|
||||
moderatedPermissions := role.GetChannelModeratedPermissions()
|
||||
for permission := range moderatedPermissions {
|
||||
assert.Equal(t, moderatedPermissions[permission], tc.Expected[permission])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user