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
Этот коммит содержится в:
Jesús Espino
2019-04-29 16:56:56 +02:00
коммит произвёл GitHub
родитель 5b70962f71
Коммит c8920588a0
41 изменённых файлов: 2785 добавлений и 259 удалений

Просмотреть файл

@@ -407,6 +407,8 @@ func (us SqlUserStore) GetAllProfiles(options *model.UserGetOptions) store.Store
OrderBy("u.Username ASC").
Offset(uint64(options.Page * options.PerPage)).Limit(uint64(options.PerPage))
query = applyViewRestrictionsFilter(query, options.ViewRestrictions, true)
query = applyRoleFilter(query, options.Role, isPostgreSQL)
if options.Inactive {
@@ -466,6 +468,8 @@ func (us SqlUserStore) GetProfiles(options *model.UserGetOptions) store.StoreCha
OrderBy("u.Username ASC").
Offset(uint64(options.Page * options.PerPage)).Limit(uint64(options.PerPage))
query = applyViewRestrictionsFilter(query, options.ViewRestrictions, true)
query = applyRoleFilter(query, options.Role, isPostgreSQL)
if options.Inactive {
@@ -632,7 +636,7 @@ func (us SqlUserStore) GetAllProfilesInChannel(channelId string, allowFromCache
})
}
func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) store.StoreChannel {
func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.
Join("TeamMembers tm ON ( tm.UserId = u.Id AND tm.DeleteAt = 0 AND tm.TeamId = ? )", teamId).
@@ -641,6 +645,8 @@ func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string,
OrderBy("u.Username ASC").
Offset(uint64(offset)).Limit(uint64(limit))
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfilesNotInChannel", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -661,7 +667,7 @@ func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string,
})
}
func (us SqlUserStore) GetProfilesWithoutTeam(offset int, limit int) store.StoreChannel {
func (us SqlUserStore) GetProfilesWithoutTeam(offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.
Where(`(
@@ -676,6 +682,8 @@ func (us SqlUserStore) GetProfilesWithoutTeam(offset int, limit int) store.Store
OrderBy("u.Username ASC").
Offset(uint64(offset)).Limit(uint64(limit))
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfilesWithoutTeam", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -696,13 +704,11 @@ func (us SqlUserStore) GetProfilesWithoutTeam(offset int, limit int) store.Store
})
}
func (us SqlUserStore) GetProfilesByUsernames(usernames []string, teamId string) store.StoreChannel {
func (us SqlUserStore) GetProfilesByUsernames(usernames []string, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery
if teamId != "" {
query = query.Join("TeamMembers tm ON (tm.UserId = u.Id AND tm.TeamId = ?)", teamId)
}
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
query = query.
Where(map[string]interface{}{
@@ -731,7 +737,7 @@ type UserWithLastActivityAt struct {
LastActivityAt int64
}
func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string, offset, limit int) store.StoreChannel {
func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string, offset, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.
Column("s.LastActivityAt").
@@ -741,6 +747,8 @@ func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string, offset, limi
OrderBy("u.Username ASC").
Offset(uint64(offset)).Limit(uint64(limit))
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetRecentlyActiveUsers", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -766,7 +774,7 @@ func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string, offset, limi
})
}
func (us SqlUserStore) GetNewUsersForTeam(teamId string, offset, limit int) store.StoreChannel {
func (us SqlUserStore) GetNewUsersForTeam(teamId string, offset, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.
Join("TeamMembers tm ON (tm.UserId = u.Id AND tm.TeamId = ?)", teamId).
@@ -774,6 +782,8 @@ func (us SqlUserStore) GetNewUsersForTeam(teamId string, offset, limit int) stor
OrderBy("u.Username ASC").
Offset(uint64(offset)).Limit(uint64(limit))
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetNewUsersForTeam", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -794,7 +804,7 @@ func (us SqlUserStore) GetNewUsersForTeam(teamId string, offset, limit int) stor
})
}
func (us SqlUserStore) GetProfileByIds(userIds []string, allowFromCache bool) store.StoreChannel {
func (us SqlUserStore) GetProfileByIds(userIds []string, allowFromCache bool, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
users := []*model.User{}
remainingUserIds := make([]string, 0)
@@ -832,6 +842,8 @@ func (us SqlUserStore) GetProfileByIds(userIds []string, allowFromCache bool) st
}).
OrderBy("u.Username ASC")
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfileByIds", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -1039,18 +1051,18 @@ func (us SqlUserStore) PermanentDelete(userId string) store.StoreChannel {
func (us SqlUserStore) Count(options model.UserCountOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := sq.Select("COUNT(Users.Id)").From("Users")
query := sq.Select("COUNT(DISTINCT u.Id)").From("Users AS u")
if !options.IncludeDeleted {
query = query.Where("Users.DeleteAt = 0")
query = query.Where("u.DeleteAt = 0")
}
if options.IncludeBotAccounts {
if options.ExcludeRegularUsers {
query = query.Join("Bots ON Users.Id = Bots.UserId")
query = query.Join("Bots ON u.Id = Bots.UserId")
}
} else {
query = query.LeftJoin("Bots ON Users.Id = Bots.UserId").Where("Bots.UserId IS NULL")
query = query.LeftJoin("Bots ON u.Id = Bots.UserId").Where("Bots.UserId IS NULL")
if options.ExcludeRegularUsers {
// Currenty this doesn't make sense because it will always return 0
result.Err = model.NewAppError("SqlUserStore.Count", "store.sql_user.count.app_error", nil, "", http.StatusInternalServerError)
@@ -1059,8 +1071,9 @@ func (us SqlUserStore) Count(options model.UserCountOptions) store.StoreChannel
}
if options.TeamId != "" {
query = query.LeftJoin("TeamMembers ON Users.Id = TeamMembers.UserId").Where("TeamMembers.TeamId = ? AND TeamMembers.DeleteAt = 0", options.TeamId)
query = query.LeftJoin("TeamMembers AS tm ON u.Id = tm.UserId").Where("tm.TeamId = ? AND tm.DeleteAt = 0", options.TeamId)
}
query = applyViewRestrictionsFilter(query, options.ViewRestrictions, false)
if us.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = query.PlaceholderFormat(sq.Dollar)
@@ -1285,6 +1298,8 @@ func (us SqlUserStore) performSearch(query sq.SelectBuilder, term string, option
query = generateSearchQuery(query, strings.Fields(term), searchType, isPostgreSQL)
}
query = applyViewRestrictionsFilter(query, options.ViewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.Search", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -1326,7 +1341,7 @@ func (us SqlUserStore) AnalyticsGetSystemAdminCount() store.StoreChannel {
})
}
func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int) store.StoreChannel {
func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.
LeftJoin("TeamMembers tm ON ( tm.UserId = u.Id AND tm.DeleteAt = 0 AND tm.TeamId = ? )", teamId).
@@ -1334,6 +1349,8 @@ func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int
OrderBy("u.Username ASC").
Offset(uint64(offset)).Limit(uint64(limit))
query = applyViewRestrictionsFilter(query, viewRestrictions, true)
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfilesNotInTeam", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -1611,3 +1628,36 @@ func (us SqlUserStore) GetChannelGroupUsers(channelID string) store.StoreChannel
result.Data = users
})
}
func applyViewRestrictionsFilter(query sq.SelectBuilder, restrictions *model.ViewUsersRestrictions, distinct bool) sq.SelectBuilder {
if restrictions == nil {
return query
}
// If you have no access to teams or channels, return and empty result.
if restrictions.Teams != nil && len(restrictions.Teams) == 0 && restrictions.Channels != nil && len(restrictions.Channels) == 0 {
return query.Where("1 = 0")
}
teams := make([]interface{}, len(restrictions.Teams))
for i, v := range restrictions.Teams {
teams[i] = v
}
channels := make([]interface{}, len(restrictions.Channels))
for i, v := range restrictions.Channels {
channels[i] = v
}
resultQuery := query
if restrictions.Teams != nil && len(restrictions.Teams) > 0 {
resultQuery = resultQuery.Join(fmt.Sprintf("TeamMembers rtm ON ( rtm.UserId = u.Id AND rtm.DeleteAt = 0 AND rtm.TeamId IN (%s))", sq.Placeholders(len(teams))), teams...)
}
if restrictions.Channels != nil && len(restrictions.Channels) > 0 {
resultQuery = resultQuery.Join(fmt.Sprintf("ChannelMembers rcm ON ( rcm.UserId = u.Id AND rcm.ChannelId IN (%s))", sq.Placeholders(len(channels))), channels...)
}
if distinct {
return resultQuery.Distinct()
}
return resultQuery
}