diff --git a/api4/team_local.go b/api4/team_local.go index 8f35695e56..ff9b4da4b3 100644 --- a/api4/team_local.go +++ b/api4/team_local.go @@ -160,10 +160,10 @@ func localInviteUsersToTeam(c *Context, w http.ResponseWriter, r *http.Request) if len(goodEmails) > 0 { var invitesWithErrors2 []*model.EmailInviteWithError if len(channels) > 0 { - invitesWithErrors2, err = c.App.Srv().EmailService.SendInviteEmailsToTeamAndChannels(team, channels, "Administrator", "mmctl "+model.NewId(), nil, goodEmails, *c.App.Config().ServiceSettings.SiteURL, nil, memberInvite.Message, true) + invitesWithErrors2, err = c.App.Srv().EmailService.SendInviteEmailsToTeamAndChannels(team, channels, "Administrator", "mmctl "+model.NewId(), nil, goodEmails, *c.App.Config().ServiceSettings.SiteURL, nil, memberInvite.Message, true, true, false) invitesWithErrors = append(invitesWithErrors, invitesWithErrors2...) } else { - err = c.App.Srv().EmailService.SendInviteEmails(team, "Administrator", "mmctl "+model.NewId(), goodEmails, *c.App.Config().ServiceSettings.SiteURL, nil, false) + err = c.App.Srv().EmailService.SendInviteEmails(team, "Administrator", "mmctl "+model.NewId(), goodEmails, *c.App.Config().ServiceSettings.SiteURL, nil, false, true, false) } if err != nil { @@ -200,7 +200,7 @@ func localInviteUsersToTeam(c *Context, w http.ResponseWriter, r *http.Request) c.Err = model.NewAppError("localInviteUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]any{"Addresses": s}, "", http.StatusBadRequest) return } - err := c.App.Srv().EmailService.SendInviteEmails(team, "Administrator", "mmctl "+model.NewId(), emailList, *c.App.Config().ServiceSettings.SiteURL, nil, false) + err := c.App.Srv().EmailService.SendInviteEmails(team, "Administrator", "mmctl "+model.NewId(), emailList, *c.App.Config().ServiceSettings.SiteURL, nil, false, true, false) if err != nil { switch { case errors.Is(err, email.NoRateLimiterError): diff --git a/app/app_iface.go b/app/app_iface.go index aed132ba5f..f3ac3315f7 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -1143,6 +1143,7 @@ type AppIface interface { UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) UserAlreadyNotifiedOnRequiredFeature(user string, feature model.MattermostPaidFeature) bool UserCanSeeOtherUser(userID string, otherUserId string) (bool, *model.AppError) + UserIsFirstAdmin(user *model.User) bool VerifyEmailFromToken(c request.CTX, userSuppliedTokenString string) *model.AppError VerifyUserEmail(userID, email string) *model.AppError ViewChannel(c request.CTX, view *model.ChannelView, userID string, currentSessionId string, collapsedThreadsSupported bool) (map[string]int64, *model.AppError) diff --git a/app/email/email.go b/app/email/email.go index d9f3882c61..ca1d7752e5 100644 --- a/app/email/email.go +++ b/app/email/email.go @@ -410,7 +410,17 @@ func (es *Service) SendMfaChangeEmail(email string, activated bool, locale, site return nil } -func (es *Service) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool) error { +func (es *Service) SendInviteEmails( + team *model.Team, + senderName string, + senderUserId string, + invites []string, + siteURL string, + reminderData *model.TeamInviteReminderData, + errorWhenNotSent bool, + isSystemAdmin bool, + isFirstAdmin bool, +) error { if es.perHourEmailRateLimiter == nil { return NoRateLimiterError } @@ -466,7 +476,8 @@ func (es *Service) SendInviteEmails(team *model.Team, senderName string, senderU mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) continue } - data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token)) + + data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s&sbr=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token), es.GetTrackFlowStartedByRole(isFirstAdmin, isSystemAdmin)) body, err := es.templatesContainer.RenderToString("invite_body", data) if err != nil { @@ -484,7 +495,19 @@ func (es *Service) SendInviteEmails(team *model.Team, senderName string, senderU return nil } -func (es *Service) SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string, errorWhenNotSent bool) error { +func (es *Service) SendGuestInviteEmails( + team *model.Team, + channels []*model.Channel, + senderName string, + senderUserId string, + senderProfileImage []byte, + invites []string, + siteURL string, + message string, + errorWhenNotSent bool, + isSystemAdmin bool, + isFirstAdmin bool, +) error { if es.perHourEmailRateLimiter == nil { return NoRateLimiterError } @@ -545,7 +568,8 @@ func (es *Service) SendGuestInviteEmails(team *model.Team, channels []*model.Cha mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) continue } - data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token)) + + data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s&sbr=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token), es.GetTrackFlowStartedByRole(isFirstAdmin, isSystemAdmin)) if !*es.config().EmailSettings.SendEmailNotifications { mlog.Info("sending invitation ", mlog.String("to", invite), mlog.String("link", data.Props["ButtonURL"].(string))) @@ -597,6 +621,8 @@ func (es *Service) SendInviteEmailsToTeamAndChannels( reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool, + isSystemAdmin bool, + isFirstAdmin bool, ) ([]*model.EmailInviteWithError, error) { if es.perHourEmailRateLimiter == nil { return nil, NoRateLimiterError @@ -694,7 +720,8 @@ func (es *Service) SendInviteEmailsToTeamAndChannels( mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) continue } - data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token)) + + data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s&sbr=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token), es.GetTrackFlowStartedByRole(isFirstAdmin, isSystemAdmin)) senderPhoto := "" embeddedFiles := make(map[string]io.Reader) diff --git a/app/email/email_test.go b/app/email/email_test.go index 5742a67c73..0fe3b237e0 100644 --- a/app/email/email_test.go +++ b/app/email/email_test.go @@ -76,7 +76,7 @@ func TestSendInviteEmails(t *testing.T) { t.Run("SendInviteEmails", func(t *testing.T) { mail.DeleteMailBox(emailTo) - err := th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, false) + err := th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, false, false, false) require.NoError(t, err) verifyMailbox(t) @@ -93,10 +93,10 @@ func TestSendInviteEmails(t *testing.T) { *cfg.EmailSettings.SMTPPort = originalPort }) - err := th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, true) + err := th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, true, false, false) require.Error(t, err) - err = th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, false) + err = th.service.SendInviteEmails(th.BasicTeam, "test-user", th.BasicUser.Id, []string{emailTo}, "http://testserver", nil, false, false, false) require.NoError(t, err) }) @@ -113,6 +113,8 @@ func TestSendInviteEmails(t *testing.T) { "http://testserver", "hello world", false, + false, + false, ) require.NoError(t, err) @@ -140,6 +142,8 @@ func TestSendInviteEmails(t *testing.T) { "http://testserver", "hello world", false, + false, + false, ) require.NoError(t, err) @@ -153,6 +157,8 @@ func TestSendInviteEmails(t *testing.T) { "http://testserver", "hello world", true, + false, + false, ) require.Error(t, err) @@ -172,6 +178,8 @@ func TestSendInviteEmails(t *testing.T) { "http://testserver", message, false, + false, + false, ) require.NoError(t, err) @@ -180,6 +188,66 @@ func TestSendInviteEmails(t *testing.T) { require.Contains(t, email.Body.HTML, "sanitized message") require.Contains(t, email.Body.Text, "sanitized message") }) + + t.Run("SendInviteEmails should contain button URL with 'started by role' param for system user", func(t *testing.T) { + mail.DeleteMailBox(emailTo) + + err := th.service.SendInviteEmails( + th.BasicTeam, + "test-user", + th.BasicUser.Id, + []string{emailTo}, + "http://testserver", + nil, + false, + false, + false, + ) + require.NoError(t, err) + + email := retrieveEmail(t) + require.Contains(t, email.Body.HTML, "&sbr=su") + }) + + t.Run("SendInviteEmails should contain button URL with 'started by role' param for system admin", func(t *testing.T) { + mail.DeleteMailBox(emailTo) + + err := th.service.SendInviteEmails( + th.BasicTeam, + "test-user", + th.BasicUser.Id, + []string{emailTo}, + "http://testserver", + nil, + false, + true, + false, + ) + require.NoError(t, err) + + email := retrieveEmail(t) + require.Contains(t, email.Body.HTML, "&sbr=sa") + }) + + t.Run("SendInviteEmails should contain button URL with 'started by role' param for first system admin", func(t *testing.T) { + mail.DeleteMailBox(emailTo) + + err := th.service.SendInviteEmails( + th.BasicTeam, + "test-user", + th.BasicUser.Id, + []string{emailTo}, + "http://testserver", + nil, + false, + true, + true, + ) + require.NoError(t, err) + + email := retrieveEmail(t) + require.Contains(t, email.Body.HTML, "&sbr=fa") + }) } func TestSendCloudUpgradedEmail(t *testing.T) { diff --git a/app/email/mocks/ServiceInterface.go b/app/email/mocks/ServiceInterface.go index dbcabb1146..08cddbe4ad 100644 --- a/app/email/mocks/ServiceInterface.go +++ b/app/email/mocks/ServiceInterface.go @@ -293,13 +293,13 @@ func (_m *ServiceInterface) SendEmailChangeVerifyEmail(newUserEmail string, loca return r0 } -// SendGuestInviteEmails provides a mock function with given fields: team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent -func (_m *ServiceInterface) SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string, errorWhenNotSent bool) error { - ret := _m.Called(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent) +// SendGuestInviteEmails provides a mock function with given fields: team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin +func (_m *ServiceInterface) SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) error { + ret := _m.Called(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin) var r0 error - if rf, ok := ret.Get(0).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, string, bool) error); ok { - r0 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent) + if rf, ok := ret.Get(0).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, string, bool, bool, bool) error); ok { + r0 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin) } else { r0 = ret.Error(0) } @@ -307,13 +307,13 @@ func (_m *ServiceInterface) SendGuestInviteEmails(team *model.Team, channels []* return r0 } -// SendInviteEmails provides a mock function with given fields: team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent -func (_m *ServiceInterface) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool) error { - ret := _m.Called(team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent) +// SendInviteEmails provides a mock function with given fields: team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent, isSystemAdmin, isFirstAdmin +func (_m *ServiceInterface) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) error { + ret := _m.Called(team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent, isSystemAdmin, isFirstAdmin) var r0 error - if rf, ok := ret.Get(0).(func(*model.Team, string, string, []string, string, *model.TeamInviteReminderData, bool) error); ok { - r0 = rf(team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent) + if rf, ok := ret.Get(0).(func(*model.Team, string, string, []string, string, *model.TeamInviteReminderData, bool, bool, bool) error); ok { + r0 = rf(team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent, isSystemAdmin, isFirstAdmin) } else { r0 = ret.Error(0) } @@ -321,13 +321,13 @@ func (_m *ServiceInterface) SendInviteEmails(team *model.Team, senderName string return r0 } -// SendInviteEmailsToTeamAndChannels provides a mock function with given fields: team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent -func (_m *ServiceInterface) SendInviteEmailsToTeamAndChannels(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool) ([]*model.EmailInviteWithError, error) { - ret := _m.Called(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent) +// SendInviteEmailsToTeamAndChannels provides a mock function with given fields: team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin +func (_m *ServiceInterface) SendInviteEmailsToTeamAndChannels(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) ([]*model.EmailInviteWithError, error) { + ret := _m.Called(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin) var r0 []*model.EmailInviteWithError - if rf, ok := ret.Get(0).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, *model.TeamInviteReminderData, string, bool) []*model.EmailInviteWithError); ok { - r0 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent) + if rf, ok := ret.Get(0).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, *model.TeamInviteReminderData, string, bool, bool, bool) []*model.EmailInviteWithError); ok { + r0 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.EmailInviteWithError) @@ -335,8 +335,8 @@ func (_m *ServiceInterface) SendInviteEmailsToTeamAndChannels(team *model.Team, } var r1 error - if rf, ok := ret.Get(1).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, *model.TeamInviteReminderData, string, bool) error); ok { - r1 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent) + if rf, ok := ret.Get(1).(func(*model.Team, []*model.Channel, string, string, []byte, []string, string, *model.TeamInviteReminderData, string, bool, bool, bool) error); ok { + r1 = rf(team, channels, senderName, senderUserId, senderProfileImage, invites, siteURL, reminderData, message, errorWhenNotSent, isSystemAdmin, isFirstAdmin) } else { r1 = ret.Error(1) } diff --git a/app/email/service.go b/app/email/service.go index 135991692e..0f9158e343 100644 --- a/app/email/service.go +++ b/app/email/service.go @@ -135,9 +135,9 @@ type ServiceInterface interface { SendUserAccessTokenAddedEmail(email, locale, siteURL string) error SendPasswordResetEmail(email string, token *model.Token, locale, siteURL string) (bool, error) SendMfaChangeEmail(email string, activated bool, locale, siteURL string) error - SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool) error - SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string, errorWhenNotSent bool) error - SendInviteEmailsToTeamAndChannels(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool) ([]*model.EmailInviteWithError, error) + SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) error + SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) error + SendInviteEmailsToTeamAndChannels(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) ([]*model.EmailInviteWithError, error) SendDeactivateAccountEmail(email string, locale, siteURL string) error SendNotificationMail(to, subject, htmlBody string) error SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string) error diff --git a/app/email/utils.go b/app/email/utils.go index 623e0c6969..b203a4f62e 100644 --- a/app/email/utils.go +++ b/app/email/utils.go @@ -34,3 +34,15 @@ func (es *Service) mailServiceConfig(replyToAddress string) *mail.SMTPConfig { } return &cfg } + +func (es *Service) GetTrackFlowStartedByRole(isFirstAdmin bool, isSystemAdmin bool) string { + trackFlowStartedByRole := "su" + + if isFirstAdmin { + trackFlowStartedByRole = "fa" + } else if isSystemAdmin { + trackFlowStartedByRole = "sa" + } + + return trackFlowStartedByRole +} diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 97f929c77d..e50746cc98 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -18052,6 +18052,23 @@ func (a *OpenTracingAppLayer) UserCanSeeOtherUser(userID string, otherUserId str return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) UserIsFirstAdmin(user *model.User) bool { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UserIsFirstAdmin") + + 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.UserIsFirstAdmin(user) + + return resultVar0 +} + func (a *OpenTracingAppLayer) UserIsInAdminRoleGroup(userID string, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UserIsInAdminRoleGroup") diff --git a/app/team.go b/app/team.go index fc69fbb652..30020e25ef 100644 --- a/app/team.go +++ b/app/team.go @@ -1461,13 +1461,15 @@ func (a *App) InviteNewUsersToTeamGracefully(memberInvite *model.MemberInvite, t if err != nil { a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } + + userIsFirstAdmin := a.UserIsFirstAdmin(user) var eErr error var invitesWithErrors2 []*model.EmailInviteWithError if len(channels) > 0 { - invitesWithErrors2, eErr = a.Srv().EmailService.SendInviteEmailsToTeamAndChannels(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), reminderData, memberInvite.Message, true) + invitesWithErrors2, eErr = a.Srv().EmailService.SendInviteEmailsToTeamAndChannels(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), reminderData, memberInvite.Message, true, user.IsSystemAdmin(), userIsFirstAdmin) inviteListWithErrors = append(inviteListWithErrors, invitesWithErrors2...) } else { - eErr = a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, goodEmails, a.GetSiteURL(), reminderData, true) + eErr = a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, goodEmails, a.GetSiteURL(), reminderData, true, user.IsSystemAdmin(), userIsFirstAdmin) } if eErr != nil { switch { @@ -1587,7 +1589,8 @@ func (a *App) InviteGuestsToChannelsGracefully(teamID string, guestsInvite *mode if err != nil { a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } - eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), guestsInvite.Message, true) + + eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), guestsInvite.Message, true, user.IsSystemAdmin(), a.UserIsFirstAdmin(user)) if eErr != nil { switch { case errors.Is(eErr, email.SendMailError): @@ -1644,7 +1647,7 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamID, senderId string) } nameFormat := *a.Config().TeamSettings.TeammateNameDisplay - eErr := a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, emailList, a.GetSiteURL(), nil, false) + eErr := a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, emailList, a.GetSiteURL(), nil, false, user.IsSystemAdmin(), a.UserIsFirstAdmin(user)) if eErr != nil { switch { case errors.Is(eErr, email.NoRateLimiterError): @@ -1686,7 +1689,8 @@ func (a *App) InviteGuestsToChannels(teamID string, guestsInvite *model.GuestsIn if err != nil { a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } - eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, guestsInvite.Emails, a.GetSiteURL(), guestsInvite.Message, false) + + eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, guestsInvite.Emails, a.GetSiteURL(), guestsInvite.Message, false, user.IsSystemAdmin(), a.UserIsFirstAdmin(user)) if eErr != nil { switch { case errors.Is(eErr, email.NoRateLimiterError): diff --git a/app/team_test.go b/app/team_test.go index 14eb0273a7..604cef33d6 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -1457,6 +1457,8 @@ func TestInviteNewUsersToTeamGracefully(t *testing.T) { "", mock.Anything, true, + false, + false, ).Once().Return(nil) th.App.Srv().EmailService = &emailServiceMock @@ -1479,6 +1481,8 @@ func TestInviteNewUsersToTeamGracefully(t *testing.T) { "", mock.Anything, true, + false, + false, ).Once().Return(email.SendMailError) th.App.Srv().EmailService = &emailServiceMock @@ -1505,6 +1509,8 @@ func TestInviteNewUsersToTeamGracefully(t *testing.T) { mock.Anything, mock.AnythingOfType("string"), true, + false, + false, ).Once().Return([]*model.EmailInviteWithError{}, nil) th.App.Srv().EmailService = &emailServiceMock @@ -1527,6 +1533,8 @@ func TestInviteNewUsersToTeamGracefully(t *testing.T) { "", mock.Anything, true, + false, + false, ).Once().Return(nil) th.App.Srv().EmailService = &emailServiceMock @@ -1557,6 +1565,8 @@ func TestInviteGuestsToChannelsGracefully(t *testing.T) { "", "", true, + false, + false, ).Once().Return(nil) th.App.Srv().EmailService = &emailServiceMock @@ -1581,6 +1591,8 @@ func TestInviteGuestsToChannelsGracefully(t *testing.T) { "", "", true, + false, + false, ).Once().Return(email.SendMailError) th.App.Srv().EmailService = &emailServiceMock diff --git a/app/user.go b/app/user.go index c0a9773e61..929d86116f 100644 --- a/app/user.go +++ b/app/user.go @@ -2671,3 +2671,25 @@ func getProfileImagePath(userID string) string { func getProfileImageDirectory(userID string) string { return filepath.Join("users", userID) } + +func (a *App) UserIsFirstAdmin(user *model.User) bool { + if !user.IsSystemAdmin() { + return false + } + + systemAdminUsers, errServer := a.Srv().Store.User().GetSystemAdminProfiles() + if errServer != nil { + mlog.Warn("Failed to get system admins to check for first admin from Mattermost.") + return false + } + + for _, systemAdminUser := range systemAdminUsers { + systemAdminUser := systemAdminUser + + if systemAdminUser.CreateAt < user.CreateAt { + return false + } + } + + return true +}