Adding new "VIEW_MEMBERS" permissions restrict the scope of users visibility (#10487)
* MM-14138: Adding new "VIEW_MEMBERS" permissions restrict the scope of users visibility * Fixing gofmt * Fixing broken tests * Addressing PR review comments from Miguel de la Cruz * Removed hack * A bit nicer and cleaner code in the UserBelongsToChannels function * Adding cluster cache invalidation for user team ids * Checking in the correct order permissions to not leek existency information * Adding restrictions to TeamMembers and User status requests * Fixing tests * Fixing status endpoint permissions checks * Adding more tests * Fixing tests * More tests and making the restrictions query based only on joins * Adding more tests * Adding more tests * fixing merge problems * Reverting status changes to avoid performance issues * Adding more tests * Fixing test * i18n extract * Adding extra method for get restrictions for a team * Add the new elasticsearch functions to search users with restrictions * Add missing translation string * Rename restrictedChannelIds to restrictedToChannels * Remove ToDo * Adding the permission to the SystemAdmin role during permissions migrations
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5b70962f71
Коммит
c8920588a0
255
app/user.go
255
app/user.go
@@ -447,8 +447,8 @@ func (a *App) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*mode
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersEtag() string {
|
||||
return fmt.Sprintf("%v.%v.%v", (<-a.Srv.Store.User().GetEtagForAllProfiles()).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress)
|
||||
func (a *App) GetUsersEtag(restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", (<-a.Srv.Store.User().GetEtagForAllProfiles()).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (a *App) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
||||
@@ -459,8 +459,8 @@ func (a *App) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, *mod
|
||||
return result.Data.([]*model.User), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInTeam(teamId string, offset int, limit int) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesNotInTeam(teamId, offset, limit)
|
||||
func (a *App) GetUsersNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesNotInTeam(teamId, offset, limit, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
@@ -476,8 +476,8 @@ func (a *App) GetUsersInTeamPage(options *model.UserGetOptions, asAdmin bool) ([
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInTeamPage(teamId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInTeam(teamId, page*perPage, perPage)
|
||||
func (a *App) GetUsersNotInTeamPage(teamId string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInTeam(teamId, page*perPage, perPage, viewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -485,12 +485,12 @@ func (a *App) GetUsersNotInTeamPage(teamId string, page int, perPage int, asAdmi
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersInTeamEtag(teamId string) string {
|
||||
return fmt.Sprintf("%v.%v.%v", (<-a.Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress)
|
||||
func (a *App) GetUsersInTeamEtag(teamId string, restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", (<-a.Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInTeamEtag(teamId string) string {
|
||||
return fmt.Sprintf("%v.%v.%v", (<-a.Srv.Store.User().GetEtagForProfilesNotInTeam(teamId)).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress)
|
||||
func (a *App) GetUsersNotInTeamEtag(teamId string, restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", (<-a.Srv.Store.User().GetEtagForProfilesNotInTeam(teamId)).Data.(string), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (a *App) GetUsersInChannel(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
|
||||
@@ -541,16 +541,16 @@ func (a *App) GetUsersInChannelPageByStatus(channelId string, page int, perPage
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit)
|
||||
func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
return result.Data.([]*model.User), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInChannel(teamId, channelId, offset, limit)
|
||||
func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, offset int, limit int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) (map[string]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInChannel(teamId, channelId, offset, limit, viewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -565,8 +565,8 @@ func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, offset in
|
||||
return userMap, nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInChannel(teamId, channelId, page*perPage, perPage)
|
||||
func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersNotInChannel(teamId, channelId, page*perPage, perPage, viewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -574,8 +574,8 @@ func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, page int
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersWithoutTeam(page*perPage, perPage)
|
||||
func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
users, err := a.GetUsersWithoutTeam(page*perPage, perPage, viewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -583,8 +583,8 @@ func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool) ([]*m
|
||||
return a.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersWithoutTeam(offset int, limit int) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesWithoutTeam(offset, limit)
|
||||
func (a *App) GetUsersWithoutTeam(offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesWithoutTeam(offset, limit, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
@@ -609,16 +609,16 @@ func (a *App) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppE
|
||||
return result.Data.([]*model.User), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersByIds(userIds []string, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfileByIds(userIds, true)
|
||||
func (a *App) GetUsersByIds(userIds []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfileByIds(userIds, viewRestrictions == nil, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
return a.sanitizeProfiles(result.Data.([]*model.User), asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesByUsernames(usernames, "")
|
||||
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesByUsernames(usernames, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
@@ -1601,9 +1601,10 @@ func (a *App) GetVerifyEmailToken(token string) (*model.Token, *model.AppError)
|
||||
}
|
||||
|
||||
// GetTotalUsersStats is used for the DM list total
|
||||
func (a *App) GetTotalUsersStats() (*model.UsersStats, *model.AppError) {
|
||||
func (a *App) GetTotalUsersStats(viewRestrictions *model.ViewUsersRestrictions) (*model.UsersStats, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().Count(model.UserCountOptions{
|
||||
IncludeBotAccounts: true,
|
||||
ViewRestrictions: viewRestrictions,
|
||||
})
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
@@ -1682,12 +1683,20 @@ func (a *App) SearchUsersInTeam(teamId string, term string, options *model.UserS
|
||||
esInterface := a.Elasticsearch
|
||||
license := a.License()
|
||||
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableAutocomplete && license != nil && *license.Features.Elasticsearch {
|
||||
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, term, options)
|
||||
listOfAllowedChannels, err := a.GetViewUsersRestrictionsForTeam(a.Session.UserId, teamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(listOfAllowedChannels) == 0 {
|
||||
return []*model.User{}, nil
|
||||
}
|
||||
|
||||
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = <-a.Srv.Store.User().GetProfileByIds(usersIds, false)
|
||||
result = <-a.Srv.Store.User().GetProfileByIds(usersIds, false, nil)
|
||||
} else {
|
||||
result = <-a.Srv.Store.User().Search(teamId, term, options)
|
||||
}
|
||||
@@ -1738,12 +1747,25 @@ func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term s
|
||||
esInterface := a.Elasticsearch
|
||||
license := a.License()
|
||||
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableAutocomplete && license != nil && *license.Features.Elasticsearch {
|
||||
uchanIds, nuchanIds, err := a.Elasticsearch.SearchUsersInChannel(teamId, channelId, term, options)
|
||||
listOfAllowedChannels, err := a.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uchan = a.Srv.Store.User().GetProfileByIds(uchanIds, false)
|
||||
nuchan = a.Srv.Store.User().GetProfileByIds(nuchanIds, false)
|
||||
if len(listOfAllowedChannels) == 0 {
|
||||
return &model.UserAutocompleteInChannel{}, nil
|
||||
}
|
||||
uchanIds := []string{}
|
||||
nuchanIds := []string{}
|
||||
if !strings.Contains(strings.Join(listOfAllowedChannels, "."), channelId) {
|
||||
nuchanIds, err = a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
|
||||
} else {
|
||||
uchanIds, nuchanIds, err = a.Elasticsearch.SearchUsersInChannel(teamId, channelId, listOfAllowedChannels, term, options)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uchan = a.Srv.Store.User().GetProfileByIds(uchanIds, false, nil)
|
||||
nuchan = a.Srv.Store.User().GetProfileByIds(nuchanIds, false, nil)
|
||||
} else {
|
||||
uchan = a.Srv.Store.User().SearchInChannel(channelId, term, options)
|
||||
nuchan = a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, options)
|
||||
@@ -1785,12 +1807,20 @@ func (a *App) AutocompleteUsersInTeam(teamId string, term string, options *model
|
||||
esInterface := a.Elasticsearch
|
||||
license := a.License()
|
||||
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableAutocomplete && license != nil && *license.Features.Elasticsearch {
|
||||
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, term, options)
|
||||
listOfAllowedChannels, err := a.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(listOfAllowedChannels) == 0 {
|
||||
return &model.UserAutocompleteInTeam{}, nil
|
||||
}
|
||||
|
||||
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = <-a.Srv.Store.User().GetProfileByIds(usersIds, false)
|
||||
result = <-a.Srv.Store.User().GetProfileByIds(usersIds, false, nil)
|
||||
} else {
|
||||
result = <-a.Srv.Store.User().Search(teamId, term, options)
|
||||
}
|
||||
@@ -1865,6 +1895,16 @@ func (a *App) UpdateOAuthUserAttrs(userData io.Reader, user *model.User, provide
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) RestrictUsersGetByPermissions(userId string, options *model.UserGetOptions) (*model.UserGetOptions, *model.AppError) {
|
||||
restrictions, err := a.GetViewUsersRestrictions(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options.ViewRestrictions = restrictions
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// FilterNonGroupTeamMembers returns the subset of the given user IDs of the users who are not members of groups
|
||||
// associated to the team.
|
||||
func (a *App) FilterNonGroupTeamMembers(userIDs []string, team *model.Team) ([]string, error) {
|
||||
@@ -1930,3 +1970,154 @@ func (a *App) FilterNonGroupChannelMembers(userIDs []string, channel *model.Chan
|
||||
|
||||
return nonMemberIDs, nil
|
||||
}
|
||||
|
||||
func (a *App) RestrictUsersSearchByPermissions(userId string, options *model.UserSearchOptions) (*model.UserSearchOptions, *model.AppError) {
|
||||
restrictions, err := a.GetViewUsersRestrictions(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options.ViewRestrictions = restrictions
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (a *App) UserCanSeeOtherUser(userId string, otherUserId string) (bool, *model.AppError) {
|
||||
if userId == otherUserId {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
restrictions, err := a.GetViewUsersRestrictions(userId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if restrictions == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(restrictions.Teams) > 0 {
|
||||
result, err := a.userBelongsToTeams(otherUserId, restrictions.Teams)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if result {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(restrictions.Channels) > 0 {
|
||||
result, err := a.userBelongsToChannels(otherUserId, restrictions.Channels)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if result {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (a *App) userBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) {
|
||||
result := <-a.Srv.Store.Team().UserBelongsToTeams(userId, teamIds)
|
||||
if result.Err != nil {
|
||||
return false, result.Err
|
||||
}
|
||||
return result.Data.(bool), nil
|
||||
}
|
||||
|
||||
func (a *App) userBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) {
|
||||
result := <-a.Srv.Store.Channel().UserBelongsToChannels(userId, channelIds)
|
||||
if result.Err != nil {
|
||||
return false, result.Err
|
||||
}
|
||||
return result.Data.(bool), nil
|
||||
}
|
||||
|
||||
func (a *App) GetViewUsersRestrictions(userId string) (*model.ViewUsersRestrictions, *model.AppError) {
|
||||
if a.HasPermissionTo(userId, model.PERMISSION_VIEW_MEMBERS) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := <-a.Srv.Store.Team().GetUserTeamIds(userId, true)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
teamIds := result.Data.([]string)
|
||||
|
||||
teamIdsWithPermission := []string{}
|
||||
teamIdsWithoutPermission := []string{}
|
||||
for _, teamId := range teamIds {
|
||||
if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) {
|
||||
teamIdsWithPermission = append(teamIdsWithPermission, teamId)
|
||||
} else {
|
||||
teamIdsWithoutPermission = append(teamIdsWithoutPermission, teamId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(teamIdsWithoutPermission) == 0 {
|
||||
return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission}, nil
|
||||
}
|
||||
|
||||
userChannelMembers := <-a.Srv.Store.Channel().GetAllChannelMembersForUser(userId, true, true)
|
||||
if userChannelMembers.Err != nil {
|
||||
return nil, userChannelMembers.Err
|
||||
}
|
||||
|
||||
channelIds := []string{}
|
||||
for channelId := range userChannelMembers.Data.(map[string]string) {
|
||||
channelIds = append(channelIds, channelId)
|
||||
}
|
||||
|
||||
return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission, Channels: channelIds}, nil
|
||||
}
|
||||
|
||||
func (a *App) GetViewUsersRestrictionsForTeam(userId string, teamId string) ([]string, *model.AppError) {
|
||||
if a.HasPermissionTo(userId, model.PERMISSION_VIEW_MEMBERS) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := <-a.Srv.Store.Channel().GetMembersForUser(teamId, userId)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
|
||||
channelIds := []string{}
|
||||
for _, membership := range *result.Data.(*model.ChannelMembers) {
|
||||
channelIds = append(channelIds, membership.ChannelId)
|
||||
}
|
||||
|
||||
return channelIds, nil
|
||||
}
|
||||
|
||||
func (a *App) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, *model.AppError) {
|
||||
var listOfAllowedChannels []string
|
||||
if viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId) {
|
||||
result := <-a.Srv.Store.Channel().GetTeamChannels(teamId)
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
channelIds := []string{}
|
||||
for _, channel := range *result.Data.(*model.ChannelList) {
|
||||
channelIds = append(channelIds, channel.Id)
|
||||
}
|
||||
|
||||
return channelIds, nil
|
||||
}
|
||||
|
||||
cresult := <-a.Srv.Store.Channel().GetChannelsByIds(viewRestrictions.Channels)
|
||||
if cresult.Err != nil {
|
||||
return nil, cresult.Err
|
||||
}
|
||||
for _, c := range cresult.Data.([]*model.Channel) {
|
||||
if c.TeamId == teamId {
|
||||
listOfAllowedChannels = append(listOfAllowedChannels, c.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return listOfAllowedChannels, nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user