mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-01 15:00:08 +08:00
Fix permissions in GetGroupsByNames (#35119)
The reliance on ViewUsersRestrictions was causing a SQL bug, since the original query did not join with the Users table. Instead, use model.GroupSearchOpts to rely on AllowReference, which should be used to filter the results in all cases, except when the user is a sysadmin (has the PermissionSysconsoleReadUserManagementGroups permission). Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
committed by
GitHub
parent
892492a0a8
commit
139ff4ded2
@@ -873,13 +873,13 @@ func getGroupsByNames(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
restrictions, appErr := c.App.GetViewUsersRestrictions(c.AppContext, c.AppContext.Session().UserId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
filterAllowReference := !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadUserManagementGroups)
|
||||
|
||||
opts := model.GroupSearchOpts{
|
||||
FilterAllowReference: filterAllowReference,
|
||||
}
|
||||
|
||||
groups, appErr := c.App.GetGroupsByNames(groupNames, restrictions)
|
||||
groups, appErr := c.App.GetGroupsByNames(groupNames, opts)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
||||
@@ -2350,6 +2350,91 @@ func TestGetGroupsByNames(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetGroupsByNamesAllowReference(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||
|
||||
// Create group with AllowReference=true
|
||||
id1 := model.NewId()
|
||||
groupAllowRef, appErr := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn-allow_" + id1,
|
||||
Name: model.NewPointer("allow" + id1),
|
||||
Source: model.GroupSourceLdap,
|
||||
RemoteId: model.NewPointer(model.NewId()),
|
||||
AllowReference: true,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Create group with AllowReference=false
|
||||
id2 := model.NewId()
|
||||
groupNoRef, appErr := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn-noref_" + id2,
|
||||
Name: model.NewPointer("noref" + id2),
|
||||
Source: model.GroupSourceLdap,
|
||||
RemoteId: model.NewPointer(model.NewId()),
|
||||
AllowReference: false,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Login as regular user
|
||||
th.LoginBasic(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
client *model.Client4
|
||||
searchNames []string
|
||||
expectedGroups []*model.Group
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "admin sees all groups",
|
||||
client: th.SystemAdminClient,
|
||||
searchNames: []string{*groupAllowRef.Name, *groupNoRef.Name},
|
||||
expectedGroups: []*model.Group{groupAllowRef, groupNoRef},
|
||||
description: "admin with sysconsole permission should see all groups",
|
||||
},
|
||||
{
|
||||
name: "admin sees group with AllowReference=false",
|
||||
client: th.SystemAdminClient,
|
||||
searchNames: []string{*groupNoRef.Name},
|
||||
expectedGroups: []*model.Group{groupNoRef},
|
||||
description: "admin should see group even when AllowReference=false",
|
||||
},
|
||||
{
|
||||
name: "regular user sees only AllowReference=true",
|
||||
client: th.Client,
|
||||
searchNames: []string{*groupAllowRef.Name, *groupNoRef.Name},
|
||||
expectedGroups: []*model.Group{groupAllowRef},
|
||||
description: "regular user should only see groups with AllowReference=true",
|
||||
},
|
||||
{
|
||||
name: "regular user cannot see AllowReference=false group",
|
||||
client: th.Client,
|
||||
searchNames: []string{*groupNoRef.Name},
|
||||
expectedGroups: []*model.Group{},
|
||||
description: "regular user should not see groups with AllowReference=false",
|
||||
},
|
||||
{
|
||||
name: "regular user sees AllowReference=true group",
|
||||
client: th.Client,
|
||||
searchNames: []string{*groupAllowRef.Name},
|
||||
expectedGroups: []*model.Group{groupAllowRef},
|
||||
description: "regular user should see groups with AllowReference=true",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
groups, resp, err := tc.client.GetGroupsByNames(context.Background(), tc.searchNames)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
assert.ElementsMatch(t, tc.expectedGroups, groups, tc.description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroupsByUserId(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
@@ -98,8 +98,8 @@ func (a *App) GetGroupsByUserId(userID string, opts model.GroupSearchOpts) ([]*m
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (a *App) GetGroupsByNames(names []string, restrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError) {
|
||||
groups, err := a.Srv().Store().Group().GetByNames(names, restrictions)
|
||||
func (a *App) GetGroupsByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) {
|
||||
groups, err := a.Srv().Store().Group().GetByNames(names, opts)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetGroupsByNames", "app.select_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -5903,11 +5903,11 @@ func (s *RetryLayerGroupStore) GetByName(name string, opts model.GroupSearchOpts
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerGroupStore) GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
||||
func (s *RetryLayerGroupStore) GetByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.GroupStore.GetByNames(names, viewRestrictions)
|
||||
result, err := s.GroupStore.GetByNames(names, opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -296,10 +296,14 @@ func (s *SqlGroupStore) GetByName(name string, opts model.GroupSearchOpts) (*mod
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
func (s *SqlGroupStore) GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
||||
func (s *SqlGroupStore) GetByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, error) {
|
||||
groups := []*model.Group{}
|
||||
query := s.userGroupsSelectQuery.Where(sq.Eq{"Name": names})
|
||||
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
|
||||
|
||||
if opts.FilterAllowReference {
|
||||
query = query.Where("AllowReference = true")
|
||||
}
|
||||
|
||||
if err := s.GetReplica().SelectBuilder(&groups, query); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find Groups by names")
|
||||
}
|
||||
|
||||
@@ -898,7 +898,7 @@ type GroupStore interface {
|
||||
CreateWithUserIds(group *model.GroupWithUserIds) (*model.Group, error)
|
||||
Get(groupID string) (*model.Group, error)
|
||||
GetByName(name string, opts model.GroupSearchOpts) (*model.Group, error)
|
||||
GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error)
|
||||
GetByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, error)
|
||||
GetByIDs(groupIDs []string) ([]*model.Group, error)
|
||||
GetByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, error)
|
||||
GetAllBySource(groupSource model.GroupSource) ([]*model.Group, error)
|
||||
|
||||
@@ -662,9 +662,9 @@ func (_m *GroupStore) GetByName(name string, opts model.GroupSearchOpts) (*model
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetByNames provides a mock function with given fields: names, viewRestrictions
|
||||
func (_m *GroupStore) GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
||||
ret := _m.Called(names, viewRestrictions)
|
||||
// GetByNames provides a mock function with given fields: names, opts
|
||||
func (_m *GroupStore) GetByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, error) {
|
||||
ret := _m.Called(names, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetByNames")
|
||||
@@ -672,19 +672,19 @@ func (_m *GroupStore) GetByNames(names []string, viewRestrictions *model.ViewUse
|
||||
|
||||
var r0 []*model.Group
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]string, *model.ViewUsersRestrictions) ([]*model.Group, error)); ok {
|
||||
return rf(names, viewRestrictions)
|
||||
if rf, ok := ret.Get(0).(func([]string, model.GroupSearchOpts) ([]*model.Group, error)); ok {
|
||||
return rf(names, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]string, *model.ViewUsersRestrictions) []*model.Group); ok {
|
||||
r0 = rf(names, viewRestrictions)
|
||||
if rf, ok := ret.Get(0).(func([]string, model.GroupSearchOpts) []*model.Group); ok {
|
||||
r0 = rf(names, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]string, *model.ViewUsersRestrictions) error); ok {
|
||||
r1 = rf(names, viewRestrictions)
|
||||
if rf, ok := ret.Get(1).(func([]string, model.GroupSearchOpts) error); ok {
|
||||
r1 = rf(names, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -4805,10 +4805,10 @@ func (s *TimerLayerGroupStore) GetByName(name string, opts model.GroupSearchOpts
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerGroupStore) GetByNames(names []string, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
||||
func (s *TimerLayerGroupStore) GetByNames(names []string, opts model.GroupSearchOpts) ([]*model.Group, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.GroupStore.GetByNames(names, viewRestrictions)
|
||||
result, err := s.GroupStore.GetByNames(names, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
||||
Reference in New Issue
Block a user