Adds direct participants to the channel invite (#30404)
* Adds direct participants to the channel invite The channel invite now contains the sanitized users that are local to the node that is sending the invite. In the event that the receiving server doesn't have those users in its local database, it can create them from the invite and correctly generate the DM or GM with them as members. * Use IsRemote instead of directly checking user attributes --------- Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es> Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
14426af461
Коммит
3ab0da1648
@@ -28,13 +28,26 @@ type channelInviteMsg struct {
|
||||
Purpose string `json:"purpose"`
|
||||
Type model.ChannelType `json:"type"`
|
||||
DirectParticipantIDs []string `json:"direct_participant_ids"`
|
||||
DirectParticipants []*model.User `json:"direct_participants"`
|
||||
}
|
||||
|
||||
func (cim channelInviteMsg) DirectParticipantsMap() map[string]*model.User {
|
||||
dim := make(map[string]*model.User)
|
||||
for _, user := range cim.DirectParticipants {
|
||||
dim[user.Id] = user
|
||||
}
|
||||
return dim
|
||||
}
|
||||
|
||||
type InviteOption func(msg *channelInviteMsg)
|
||||
|
||||
func WithDirectParticipantID(participantID string) InviteOption {
|
||||
func WithDirectParticipant(participant *model.User) InviteOption {
|
||||
return func(msg *channelInviteMsg) {
|
||||
msg.DirectParticipantIDs = append(msg.DirectParticipantIDs, participantID)
|
||||
msg.DirectParticipantIDs = append(msg.DirectParticipantIDs, participant.Id)
|
||||
// if the participant is local, send it as part of the invite payload
|
||||
if !participant.IsRemote() {
|
||||
msg.DirectParticipants = append(msg.DirectParticipants, sanitizeUserForSync(participant))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +368,32 @@ func (scs *Service) handleChannelCreation(invite channelInviteMsg, rc *model.Rem
|
||||
return channel, true, nil
|
||||
}
|
||||
|
||||
// getOrCreateUser will try to fetch a user by its ID from the
|
||||
// database and if it fails, it will try to create it if is present in
|
||||
// the participantsMap
|
||||
func (scs *Service) getOrCreateUser(userID string, participantsMap map[string]*model.User, rc *model.RemoteCluster) (*model.User, error) {
|
||||
user, err := scs.server.GetStore().User().Get(context.TODO(), userID)
|
||||
if err == nil {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
inviteUser, ok := participantsMap[userID]
|
||||
if !ok {
|
||||
// at this point we couldn't fetch the user nor we can create
|
||||
// it from the invite information, so we return an error
|
||||
return nil, fmt.Errorf("cannot fetch user `%q`: %w", userID, err)
|
||||
}
|
||||
|
||||
var rctx request.CTX = request.EmptyContext(scs.server.Log())
|
||||
inviteUser.RemoteId = model.NewPointer(rc.RemoteId)
|
||||
user, iErr := scs.insertSyncUser(rctx, inviteUser, nil, rc)
|
||||
if iErr != nil {
|
||||
return nil, fmt.Errorf("cannot create user `%q` for remote `%q`: %w", inviteUser.Id, rc.RemoteId, iErr)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// createDirectChannel creates a DM channel, or fetches an existing channel, and returns the channel plus a boolean
|
||||
// indicating if the channel is new.
|
||||
func (scs *Service) createDirectChannel(invite channelInviteMsg, rc *model.RemoteCluster) (*model.Channel, bool, error) {
|
||||
@@ -362,14 +401,16 @@ func (scs *Service) createDirectChannel(invite channelInviteMsg, rc *model.Remot
|
||||
return nil, false, fmt.Errorf("cannot create direct channel `%s` insufficient participant count `%d`", invite.ChannelId, len(invite.DirectParticipantIDs))
|
||||
}
|
||||
|
||||
user1, err := scs.server.GetStore().User().Get(context.TODO(), invite.DirectParticipantIDs[0])
|
||||
participantsMap := invite.DirectParticipantsMap()
|
||||
|
||||
user1, err := scs.getOrCreateUser(invite.DirectParticipantIDs[0], participantsMap, rc)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("cannot create direct channel `%s` cannot fetch user1 (%s): %w", invite.ChannelId, invite.DirectParticipantIDs[0], err)
|
||||
return nil, false, fmt.Errorf("cannot create direct channel `%s` from invite: %w", invite.ChannelId, err)
|
||||
}
|
||||
|
||||
user2, err := scs.server.GetStore().User().Get(context.TODO(), invite.DirectParticipantIDs[1])
|
||||
user2, err := scs.getOrCreateUser(invite.DirectParticipantIDs[1], participantsMap, rc)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("cannot create direct channel `%s` cannot fetch user2 (%s): %w", invite.ChannelId, invite.DirectParticipantIDs[1], err)
|
||||
return nil, false, fmt.Errorf("cannot create direct channel `%s` from invite: %w", invite.ChannelId, err)
|
||||
}
|
||||
|
||||
// determine the remote user
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
var (
|
||||
mockTypeChannel = mock.AnythingOfType("*model.Channel")
|
||||
mockTypeUser = mock.AnythingOfType("*model.User")
|
||||
mockTypeString = mock.AnythingOfType("string")
|
||||
mockTypeReqContext = mock.AnythingOfType("*request.Context")
|
||||
mockTypeContext = mock.MatchedBy(func(ctx context.Context) bool { return true })
|
||||
@@ -264,18 +265,22 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
||||
t.Run("DM channels", func(t *testing.T) {
|
||||
var testRemoteID = model.NewId()
|
||||
testCases := []struct {
|
||||
desc string
|
||||
user1 *model.User
|
||||
user2 *model.User
|
||||
canSee bool
|
||||
expectSuccess bool
|
||||
desc string
|
||||
user1 *model.User
|
||||
user2 *model.User
|
||||
canSee bool
|
||||
expectSuccess bool
|
||||
user2InDB bool
|
||||
user2InParticipants bool
|
||||
}{
|
||||
{"valid users", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, true, true},
|
||||
{"swapped users", &model.User{Id: model.NewId()}, &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, true, true},
|
||||
{"two remotes", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, true, false},
|
||||
{"two locals", &model.User{Id: model.NewId()}, &model.User{Id: model.NewId()}, true, false},
|
||||
{"can't see", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, false, false},
|
||||
{"invalid remoteid", &model.User{Id: model.NewId(), RemoteId: model.NewPointer("bogus")}, &model.User{Id: model.NewId()}, true, false},
|
||||
{"valid users", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, true, true, true, false},
|
||||
{"swapped users", &model.User{Id: model.NewId()}, &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, true, true, true, false},
|
||||
{"two remotes", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, true, false, true, false},
|
||||
{"two locals", &model.User{Id: model.NewId()}, &model.User{Id: model.NewId()}, true, false, true, false},
|
||||
{"can't see", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, false, false, true, false},
|
||||
{"invalid remoteid", &model.User{Id: model.NewId(), RemoteId: model.NewPointer("bogus")}, &model.User{Id: model.NewId()}, true, false, true, false},
|
||||
{"user2 not in DB but in participants", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, true, true, false, true},
|
||||
{"user2 not in DB and not in participants", &model.User{Id: model.NewId(), RemoteId: &testRemoteID}, &model.User{Id: model.NewId()}, true, false, false, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -298,6 +303,12 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
||||
Type: model.ChannelTypeDirect,
|
||||
DirectParticipantIDs: []string{tc.user1.Id, tc.user2.Id},
|
||||
}
|
||||
|
||||
// Add participants to the invitation if needed
|
||||
if tc.user2InParticipants {
|
||||
invitation.DirectParticipants = append(invitation.DirectParticipants, tc.user2)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(invitation)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -313,8 +324,20 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
||||
mockUserStore := mocks.UserStore{}
|
||||
mockUserStore.On("Get", mockTypeContext, tc.user1.Id).
|
||||
Return(tc.user1, nil)
|
||||
mockUserStore.On("Get", mockTypeContext, tc.user2.Id).
|
||||
Return(tc.user2, nil)
|
||||
if tc.user2InDB {
|
||||
mockUserStore.On("Get", mockTypeContext, tc.user2.Id).
|
||||
Return(tc.user2, nil)
|
||||
} else {
|
||||
mockUserStore.On("Get", mockTypeContext, tc.user2.Id).
|
||||
Return(nil, &store.ErrNotFound{})
|
||||
}
|
||||
|
||||
if tc.user2InParticipants {
|
||||
mockUserStore.On("Save", mock.AnythingOfType("*request.Context"),
|
||||
mock.MatchedBy(func(u *model.User) bool {
|
||||
return u.Id == tc.user2.Id
|
||||
})).Return(tc.user2, nil)
|
||||
}
|
||||
|
||||
mockChannelStore.On("Get", invitation.ChannelId, true).Return(nil, errors.New("boom"))
|
||||
mockChannelStore.On("GetByName", "", mockTypeString, true).Return(nil, &store.ErrNotFound{})
|
||||
@@ -332,6 +355,7 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
||||
mockApp.On("GetOrCreateDirectChannel", mockTypeReqContext, mockTypeString, mockTypeString, mock.AnythingOfType("model.ChannelOption")).
|
||||
Return(channel, nil).Maybe()
|
||||
mockApp.On("UserCanSeeOtherUser", mockTypeReqContext, mockTypeString, mockTypeString).Return(tc.canSee, nil).Maybe()
|
||||
mockApp.On("NotifySharedChannelUserUpdate", mockTypeUser).Return().Maybe()
|
||||
|
||||
defer mockApp.AssertExpectations(t)
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user