MM-49813 - validate inviter permissions when sending invitations to private channels (#22119)
* MM-49813 - validate members invitation permissions to private channels * Validate on the receiver side that the sender id still has permissions to invite * add translations * fix texts * simplify implementation * remove local test hardcoded data * add test to user_test * fix vet warning * Add comments to validate user permissions function Co-authored-by: Martin Kraft <martin@upspin.org> * regenerate app layer iface * fix unit test * fix app user_tests --------- Co-authored-by: Martin Kraft <martin@upspin.org>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e055df27e8
Коммит
ae3c21dd79
@@ -394,6 +394,8 @@ type AppIface interface {
|
||||
// UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as
|
||||
// admins in the given syncable.
|
||||
UserIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError)
|
||||
// ValidateUserPermissionsOnChannels filters channelIds based on whether userId is authorized to manage channel members. Unauthorized channels are removed from the returned list.
|
||||
ValidateUserPermissionsOnChannels(c request.CTX, userId string, channelIds []string) []string
|
||||
// VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate.
|
||||
VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError
|
||||
AccountMigration() einterfaces.AccountMigrationInterface
|
||||
|
||||
@@ -2651,6 +2651,28 @@ func (a *App) IsCRTEnabledForUser(c request.CTX, userID string) bool {
|
||||
return threadsEnabled
|
||||
}
|
||||
|
||||
// ValidateUserPermissionsOnChannels filters channelIds based on whether userId is authorized to manage channel members. Unauthorized channels are removed from the returned list.
|
||||
func (a *App) ValidateUserPermissionsOnChannels(c request.CTX, userId string, channelIds []string) []string {
|
||||
var allowedChannelIds []string
|
||||
|
||||
for _, channelId := range channelIds {
|
||||
channel, err := a.GetChannel(c, channelId)
|
||||
if err != nil {
|
||||
mlog.Info("Invite users to team - couldn't get channel " + channelId)
|
||||
continue
|
||||
}
|
||||
|
||||
if channel.Type == model.ChannelTypePrivate && a.HasPermissionToChannel(c, userId, channelId, model.PermissionManagePrivateChannelMembers) {
|
||||
allowedChannelIds = append(allowedChannelIds, channelId)
|
||||
} else if channel.Type == model.ChannelTypeOpen && a.HasPermissionToChannel(c, userId, channelId, model.PermissionManagePublicChannelMembers) {
|
||||
allowedChannelIds = append(allowedChannelIds, channelId)
|
||||
} else {
|
||||
mlog.Info("Invite users to team - no permission to add members to that channel. UserId: " + userId + " ChannelId: " + channelId)
|
||||
}
|
||||
}
|
||||
return allowedChannelIds
|
||||
}
|
||||
|
||||
// MarkChanelAsUnreadFromPost will take a post and set the channel as unread from that one.
|
||||
func (a *App) MarkChannelAsUnreadFromPost(c request.CTX, postID string, userID string, collapsedThreadsSupported bool) (*model.ChannelUnreadAt, *model.AppError) {
|
||||
if !collapsedThreadsSupported || !a.IsCRTEnabledForUser(c, userID) {
|
||||
|
||||
@@ -704,6 +704,7 @@ func (es *Service) SendInviteEmailsToTeamAndChannels(
|
||||
"teamId": team.Id,
|
||||
"email": invite,
|
||||
"channels": strings.Join(channelIDs, " "),
|
||||
"senderId": senderUserId,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -18577,6 +18577,23 @@ func (a *OpenTracingAppLayer) UserIsInAdminRoleGroup(userID string, syncableID s
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ValidateUserPermissionsOnChannels(c request.CTX, userId string, channelIds []string) []string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ValidateUserPermissionsOnChannels")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.ValidateUserPermissionsOnChannels(c, userId, channelIds)
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) VerifyEmailFromToken(c request.CTX, userSuppliedTokenString string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.VerifyEmailFromToken")
|
||||
|
||||
10
app/user.go
10
app/user.go
@@ -70,7 +70,15 @@ func (a *App) CreateUserWithToken(c request.CTX, user *model.User, token *model.
|
||||
}
|
||||
}
|
||||
|
||||
channels, nErr := a.Srv().Store().Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "), false)
|
||||
// find the sender id and grab the channels in order to validate
|
||||
// the sender id still belongs to team and to private channels
|
||||
senderId := tokenData["senderId"]
|
||||
channelIds := strings.Split(tokenData["channels"], " ")
|
||||
|
||||
// filter the channels the original inviter has still permissions over
|
||||
channelIds = a.ValidateUserPermissionsOnChannels(c, senderId, channelIds)
|
||||
|
||||
channels, nErr := a.Srv().Store().Channel().GetChannelsByIds(channelIds, false)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("CreateUserWithToken", "app.channel.get_channels_by_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
|
||||
@@ -922,7 +922,7 @@ func TestCreateUserWithToken(t *testing.T) {
|
||||
invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||||
token := model.NewToken(
|
||||
TokenTypeGuestInvitation,
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id}),
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||||
)
|
||||
|
||||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||||
@@ -953,11 +953,11 @@ func TestCreateUserWithToken(t *testing.T) {
|
||||
grantedInvitationEmail := strings.ToLower(model.NewId()) + "other-email@restricted.com"
|
||||
forbiddenDomainToken := model.NewToken(
|
||||
TokenTypeGuestInvitation,
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": forbiddenInvitationEmail, "channels": th.BasicChannel.Id}),
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": forbiddenInvitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||||
)
|
||||
grantedDomainToken := model.NewToken(
|
||||
TokenTypeGuestInvitation,
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": grantedInvitationEmail, "channels": th.BasicChannel.Id}),
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": grantedInvitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||||
)
|
||||
require.NoError(t, th.App.Srv().Store().Token().Save(forbiddenDomainToken))
|
||||
require.NoError(t, th.App.Srv().Store().Token().Save(grantedDomainToken))
|
||||
@@ -1001,7 +1001,7 @@ func TestCreateUserWithToken(t *testing.T) {
|
||||
invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||||
token := model.NewToken(
|
||||
TokenTypeGuestInvitation,
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id}),
|
||||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||||
)
|
||||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||||
guest := model.User{
|
||||
|
||||
Ссылка в новой задаче
Block a user