MM-22153: Adds tracking of channel moderation. (#14095)
* MM-22153: Adds tracking of channel moderation. * MM-22153: Fixes test setup. * MM-22153: Split role constants into scope and type.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4823b3861a
Коммит
dd0b0a3d67
@@ -54,6 +54,7 @@ const (
|
|||||||
TRACK_PERMISSIONS_TEAM_SCHEMES = "permissions_team_schemes"
|
TRACK_PERMISSIONS_TEAM_SCHEMES = "permissions_team_schemes"
|
||||||
TRACK_ELASTICSEARCH = "elasticsearch"
|
TRACK_ELASTICSEARCH = "elasticsearch"
|
||||||
TRACK_GROUPS = "groups"
|
TRACK_GROUPS = "groups"
|
||||||
|
TRACK_CHANNEL_MODERATION = "channel_moderation"
|
||||||
|
|
||||||
TRACK_ACTIVITY = "activity"
|
TRACK_ACTIVITY = "activity"
|
||||||
TRACK_LICENSE = "license"
|
TRACK_LICENSE = "license"
|
||||||
@@ -76,6 +77,7 @@ func (a *App) sendDailyDiagnostics(override bool) {
|
|||||||
a.trackPermissions()
|
a.trackPermissions()
|
||||||
a.trackElasticsearch()
|
a.trackElasticsearch()
|
||||||
a.trackGroups()
|
a.trackGroups()
|
||||||
|
a.trackChannelModeration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,3 +971,62 @@ func (a *App) trackGroups() {
|
|||||||
"distinct_group_member_count": distinctGroupMemberCount,
|
"distinct_group_member_count": distinctGroupMemberCount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) trackChannelModeration() {
|
||||||
|
channelSchemeCount, err := a.Srv().Store.Scheme().CountByScope(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
createPostUser, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_CREATE_POST.Id, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
createPostGuest, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_CREATE_POST.Id, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// only need to track one of 'add_reaction' or 'remove_reaction` because they're both toggled together by the channel moderation feature
|
||||||
|
postReactionsUser, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_ADD_REACTION.Id, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
postReactionsGuest, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_ADD_REACTION.Id, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// only need to track one of 'manage_public_channel_members' or 'manage_private_channel_members` because they're both toggled together by the channel moderation feature
|
||||||
|
manageMembersUser, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
useChannelMentionsUser, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
useChannelMentionsGuest, err := a.Srv().Store.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SendDiagnostic(TRACK_CHANNEL_MODERATION, map[string]interface{}{
|
||||||
|
"channel_scheme_count": channelSchemeCount,
|
||||||
|
|
||||||
|
"create_post_user_disabled_count": createPostUser,
|
||||||
|
"create_post_guest_disabled_count": createPostGuest,
|
||||||
|
|
||||||
|
"post_reactions_user_disabled_count": postReactionsUser,
|
||||||
|
"post_reactions_guest_disabled_count": postReactionsGuest,
|
||||||
|
|
||||||
|
"manage_members_user_disabled_count": manageMembersUser, // the UI does not allow this to be removed for guests
|
||||||
|
|
||||||
|
"use_channel_mentions_user_disabled_count": useChannelMentionsUser,
|
||||||
|
"use_channel_mentions_guest_disabled_count": useChannelMentionsGuest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RoleType string
|
||||||
|
type RoleScope string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SYSTEM_GUEST_ROLE_ID = "system_guest"
|
SYSTEM_GUEST_ROLE_ID = "system_guest"
|
||||||
SYSTEM_USER_ROLE_ID = "system_user"
|
SYSTEM_USER_ROLE_ID = "system_user"
|
||||||
@@ -30,6 +33,14 @@ const (
|
|||||||
ROLE_NAME_MAX_LENGTH = 64
|
ROLE_NAME_MAX_LENGTH = 64
|
||||||
ROLE_DISPLAY_NAME_MAX_LENGTH = 128
|
ROLE_DISPLAY_NAME_MAX_LENGTH = 128
|
||||||
ROLE_DESCRIPTION_MAX_LENGTH = 1024
|
ROLE_DESCRIPTION_MAX_LENGTH = 1024
|
||||||
|
|
||||||
|
RoleScopeSystem RoleScope = "System"
|
||||||
|
RoleScopeTeam RoleScope = "Team"
|
||||||
|
RoleScopeChannel RoleScope = "Channel"
|
||||||
|
|
||||||
|
RoleTypeGuest RoleType = "Guest"
|
||||||
|
RoleTypeUser RoleType = "User"
|
||||||
|
RoleTypeAdmin RoleType = "Admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
|
|||||||
@@ -322,3 +322,30 @@ func (s *SqlSchemeStore) PermanentDeleteAll() *model.AppError {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SqlSchemeStore) CountByScope(scope string) (int64, *model.AppError) {
|
||||||
|
count, err := s.GetReplica().SelectInt("SELECT count(*) FROM Schemes WHERE Scope = :Scope AND DeleteAt = 0", map[string]interface{}{"Scope": scope})
|
||||||
|
if err != nil {
|
||||||
|
return int64(0), model.NewAppError("SqlSchemeStore.CountByScope", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SqlSchemeStore) CountWithoutPermission(schemeScope, permissionID string, roleScope model.RoleScope, roleType model.RoleType) (int64, *model.AppError) {
|
||||||
|
joinCol := fmt.Sprintf("Default%s%sRole", roleScope, roleType)
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT
|
||||||
|
count(*)
|
||||||
|
FROM Schemes
|
||||||
|
JOIN Roles ON Roles.Name = Schemes.%s
|
||||||
|
WHERE
|
||||||
|
Schemes.DeleteAt = 0 AND
|
||||||
|
Schemes.Scope = '%s' AND
|
||||||
|
Roles.Permissions NOT LIKE '%%%s%%'
|
||||||
|
`, joinCol, schemeScope, permissionID)
|
||||||
|
count, err := s.GetReplica().SelectInt(query)
|
||||||
|
if err != nil {
|
||||||
|
return int64(0), model.NewAppError("SqlSchemeStore.CountWithoutPermission", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
@@ -580,6 +580,8 @@ type SchemeStore interface {
|
|||||||
GetAllPage(scope string, offset int, limit int) ([]*model.Scheme, *model.AppError)
|
GetAllPage(scope string, offset int, limit int) ([]*model.Scheme, *model.AppError)
|
||||||
Delete(schemeId string) (*model.Scheme, *model.AppError)
|
Delete(schemeId string) (*model.Scheme, *model.AppError)
|
||||||
PermanentDeleteAll() *model.AppError
|
PermanentDeleteAll() *model.AppError
|
||||||
|
CountByScope(scope string) (int64, *model.AppError)
|
||||||
|
CountWithoutPermission(scope, permissionID string, roleScope model.RoleScope, roleType model.RoleType) (int64, *model.AppError)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TermsOfServiceStore interface {
|
type TermsOfServiceStore interface {
|
||||||
|
|||||||
@@ -14,6 +14,52 @@ type SchemeStore struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountByScope provides a mock function with given fields: scope
|
||||||
|
func (_m *SchemeStore) CountByScope(scope string) (int64, *model.AppError) {
|
||||||
|
ret := _m.Called(scope)
|
||||||
|
|
||||||
|
var r0 int64
|
||||||
|
if rf, ok := ret.Get(0).(func(string) int64); ok {
|
||||||
|
r0 = rf(scope)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 *model.AppError
|
||||||
|
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
|
||||||
|
r1 = rf(scope)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(*model.AppError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountWithoutPermission provides a mock function with given fields: scope, permissionID, roleScope, roleType
|
||||||
|
func (_m *SchemeStore) CountWithoutPermission(scope string, permissionID string, roleScope model.RoleScope, roleType model.RoleType) (int64, *model.AppError) {
|
||||||
|
ret := _m.Called(scope, permissionID, roleScope, roleType)
|
||||||
|
|
||||||
|
var r0 int64
|
||||||
|
if rf, ok := ret.Get(0).(func(string, string, model.RoleScope, model.RoleType) int64); ok {
|
||||||
|
r0 = rf(scope, permissionID, roleScope, roleType)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 *model.AppError
|
||||||
|
if rf, ok := ret.Get(1).(func(string, string, model.RoleScope, model.RoleType) *model.AppError); ok {
|
||||||
|
r1 = rf(scope, permissionID, roleScope, roleType)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(*model.AppError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: schemeId
|
// Delete provides a mock function with given fields: schemeId
|
||||||
func (_m *SchemeStore) Delete(schemeId string) (*model.Scheme, *model.AppError) {
|
func (_m *SchemeStore) Delete(schemeId string) (*model.Scheme, *model.AppError) {
|
||||||
ret := _m.Called(schemeId)
|
ret := _m.Called(schemeId)
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ func TestSchemeStore(t *testing.T, ss store.Store) {
|
|||||||
t.Run("Delete", func(t *testing.T) { testSchemeStoreDelete(t, ss) })
|
t.Run("Delete", func(t *testing.T) { testSchemeStoreDelete(t, ss) })
|
||||||
t.Run("PermanentDeleteAll", func(t *testing.T) { testSchemeStorePermanentDeleteAll(t, ss) })
|
t.Run("PermanentDeleteAll", func(t *testing.T) { testSchemeStorePermanentDeleteAll(t, ss) })
|
||||||
t.Run("GetByName", func(t *testing.T) { testSchemeStoreGetByName(t, ss) })
|
t.Run("GetByName", func(t *testing.T) { testSchemeStoreGetByName(t, ss) })
|
||||||
|
t.Run("CountByScope", func(t *testing.T) { testSchemeStoreCountByScope(t, ss) })
|
||||||
|
t.Run("CountWithoutPermission", func(t *testing.T) { testCountWithoutPermission(t, ss) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultRoles(t *testing.T, ss store.Store) {
|
func createDefaultRoles(t *testing.T, ss store.Store) {
|
||||||
@@ -486,3 +488,145 @@ func testSchemeStorePermanentDeleteAll(t *testing.T, ss store.Store) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Empty(t, schemes)
|
assert.Empty(t, schemes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSchemeStoreCountByScope(t *testing.T, ss store.Store) {
|
||||||
|
testCounts := func(expectedTeamCount, expectedChannelCount int) {
|
||||||
|
actualCount, err := ss.Scheme().CountByScope(model.SCHEME_SCOPE_TEAM)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(expectedTeamCount), actualCount)
|
||||||
|
|
||||||
|
actualCount, err = ss.Scheme().CountByScope(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(expectedChannelCount), actualCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
createScheme := func(scope string) {
|
||||||
|
_, err := ss.Scheme().Save(&model.Scheme{
|
||||||
|
Name: model.NewId(),
|
||||||
|
DisplayName: model.NewId(),
|
||||||
|
Description: model.NewId(),
|
||||||
|
Scope: scope,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ss.Scheme().PermanentDeleteAll()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
createScheme(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
createScheme(model.SCHEME_SCOPE_TEAM)
|
||||||
|
testCounts(1, 1)
|
||||||
|
createScheme(model.SCHEME_SCOPE_TEAM)
|
||||||
|
testCounts(2, 1)
|
||||||
|
createScheme(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
testCounts(2, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCountWithoutPermission(t *testing.T, ss store.Store) {
|
||||||
|
perm := model.PERMISSION_CREATE_POST.Id
|
||||||
|
|
||||||
|
createScheme := func(scope string) *model.Scheme {
|
||||||
|
scheme, err := ss.Scheme().Save(&model.Scheme{
|
||||||
|
Name: model.NewId(),
|
||||||
|
DisplayName: model.NewId(),
|
||||||
|
Description: model.NewId(),
|
||||||
|
Scope: scope,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoles := func(scheme *model.Scheme) (channelUser, channelGuest *model.Role) {
|
||||||
|
var err *model.AppError
|
||||||
|
channelUser, err = ss.Role().GetByName(scheme.DefaultChannelUserRole)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, channelUser)
|
||||||
|
channelGuest, err = ss.Role().GetByName(scheme.DefaultChannelGuestRole)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, channelGuest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamScheme1 := createScheme(model.SCHEME_SCOPE_TEAM)
|
||||||
|
defer ss.Scheme().Delete(teamScheme1.Id)
|
||||||
|
teamScheme2 := createScheme(model.SCHEME_SCOPE_TEAM)
|
||||||
|
defer ss.Scheme().Delete(teamScheme2.Id)
|
||||||
|
channelScheme1 := createScheme(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
defer ss.Scheme().Delete(channelScheme1.Id)
|
||||||
|
channelScheme2 := createScheme(model.SCHEME_SCOPE_CHANNEL)
|
||||||
|
defer ss.Scheme().Delete(channelScheme2.Id)
|
||||||
|
|
||||||
|
ts1User, ts1Guest := getRoles(teamScheme1)
|
||||||
|
ts2User, ts2Guest := getRoles(teamScheme2)
|
||||||
|
cs1User, cs1Guest := getRoles(channelScheme1)
|
||||||
|
cs2User, cs2Guest := getRoles(channelScheme2)
|
||||||
|
|
||||||
|
allRoles := []*model.Role{
|
||||||
|
ts1User,
|
||||||
|
ts1Guest,
|
||||||
|
ts2User,
|
||||||
|
ts2Guest,
|
||||||
|
cs1User,
|
||||||
|
cs1Guest,
|
||||||
|
cs2User,
|
||||||
|
cs2Guest,
|
||||||
|
}
|
||||||
|
|
||||||
|
teamUserCount, err := ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_TEAM, perm, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(0), teamUserCount)
|
||||||
|
|
||||||
|
teamGuestCount, err := ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_TEAM, perm, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(0), teamGuestCount)
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
removePermissionFromRole *model.Role
|
||||||
|
expectTeamSchemeChannelUserCount int
|
||||||
|
expectTeamSchemeChannelGuestCount int
|
||||||
|
expectChannelSchemeChannelUserCount int
|
||||||
|
expectChannelSchemeChannelGuestCount int
|
||||||
|
}{
|
||||||
|
{ts1User, 1, 0, 0, 0},
|
||||||
|
{ts1Guest, 1, 1, 0, 0},
|
||||||
|
{ts2User, 2, 1, 0, 0},
|
||||||
|
{ts2Guest, 2, 2, 0, 0},
|
||||||
|
{cs1User, 2, 2, 1, 0},
|
||||||
|
{cs1Guest, 2, 2, 1, 1},
|
||||||
|
{cs2User, 2, 2, 2, 1},
|
||||||
|
{cs2Guest, 2, 2, 2, 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
removePermission := func(targetRole *model.Role) {
|
||||||
|
roleMatched := false
|
||||||
|
for _, role := range allRoles {
|
||||||
|
if targetRole == role {
|
||||||
|
roleMatched = true
|
||||||
|
role.Permissions = []string{}
|
||||||
|
_, err = ss.Role().Save(role)
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, roleMatched)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
removePermission(test.removePermissionFromRole)
|
||||||
|
|
||||||
|
count, err := ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_TEAM, perm, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(test.expectTeamSchemeChannelUserCount), count)
|
||||||
|
|
||||||
|
count, err = ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_TEAM, perm, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(test.expectTeamSchemeChannelGuestCount), count)
|
||||||
|
|
||||||
|
count, err = ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, perm, model.RoleScopeChannel, model.RoleTypeUser)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(test.expectChannelSchemeChannelUserCount), count)
|
||||||
|
|
||||||
|
count, err = ss.Scheme().CountWithoutPermission(model.SCHEME_SCOPE_CHANNEL, perm, model.RoleScopeChannel, model.RoleTypeGuest)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, int64(test.expectChannelSchemeChannelGuestCount), count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user