* MM-14139: Creating permissions for invite/promote/demote guests (#10778)

* MM-14139: Creating permissions for invite/promote/demote guests

* Fixing tests

* Adding invite guest api endpoint (#10792)

* Adding invite guest api endpoint

* Adding i18n

* Adding some tests

* WIP

* Migrating Token.Extra info to bigger size (2048)

* Fixing tests

* Adding client function for invite guests

* Adding send guests invites tests

* Renaming file from guest to guest_invite

* Adding Promote/Demote users from/to guest endpoints (#10791)

* Adding Promote/Demote users from/to guest endpoints

* Adding i18n translations

* Adding the client functions

* Using getQueryBuilder function

* Addressing PR review comments

* Adding default channels to users on promte from guest (#10851)

* Adding default channels to users on promte from guest

* Addressing PR review comments

* Fixing merge problems

* Sending websockets events on promote/demote (#11403)

* Sending websockets events on promote/demote

* Fixing merge problems

* Fixing govet shadowing problem

* Fixing feature branch tests

* Avoiding leaking users data through websockets for guest accounts (#11489)

* Avoiding leaking users data through websockets for guest accounts

* Adding tests and fixing code error

* Fixing i18n

* Allow to enable/disable guests and other extra config settings (#11481)

* Allow to enable/disable guests and other extra config settings

* Fixing tests and moving license and config validation to api level

* Update api4/role_test.go

Co-Authored-By: George Goldberg <george@gberg.me>

* Update api4/role_test.go

Co-Authored-By: George Goldberg <george@gberg.me>

* Fixing typo

* fixing tests

* Managing correctly the guest channel leave behavior (#11578)

* MM-15134: Removing guests from teams or system on leave channels if needed

* WIP

* No deactivating the guest user when leave the last team

* Adding a couple of tests

* Fixing shadow variables

* Fixing tests

* fixing tests

* fixing shadow variables

* Adding guest counts for channel stats (#11646)

* Adding guest counts for channel stats

* Adding tests

* Fixing tests

* Fixing guest domain restrictions (#11660)

* Adding needed migration for the database

* Fixing migration
Этот коммит содержится в:
Jesús Espino
2019-07-22 22:13:39 +02:00
коммит произвёл GitHub
родитель fdde7c8287
Коммит fe8a0f6485
48 изменённых файлов: 2630 добавлений и 133 удалений

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

@@ -486,7 +486,13 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount}
guestCount, err := c.App.GetChannelGuestCount(c.Params.ChannelId)
if err != nil {
c.Err = err
return
}
stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount, GuestCount: guestCount}
w.Write([]byte(stats.ToJson()))
}

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

@@ -97,6 +97,10 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) {
}
if c.App.License() == nil && patch.Permissions != nil {
if oldRole.Name == "system_guest" || oldRole.Name == "team_guest" || oldRole.Name == "channel_guest" {
c.Err = model.NewAppError("Api4.PatchRoles", "api.roles.patch_roles.license.error", nil, "", http.StatusNotImplemented)
return
}
allowedPermissions := []string{
model.PERMISSION_CREATE_TEAM.Id,
model.PERMISSION_MANAGE_INCOMING_WEBHOOKS.Id,
@@ -125,6 +129,11 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
if c.App.License() != nil && (oldRole.Name == "system_guest" || oldRole.Name == "team_guest" || oldRole.Name == "channel_guest") && !*c.App.License().Features.GuestAccountsPermissions {
c.Err = model.NewAppError("Api4.PatchRoles", "api.roles.patch_roles.license.error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return

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

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
)
@@ -193,7 +194,9 @@ func TestPatchRole(t *testing.T) {
CheckNotImplementedStatus(t, resp)
// Add a license.
th.App.SetLicense(model.NewTestLicense())
license := model.NewTestLicense()
license.Features.GuestAccountsPermissions = model.NewBool(false)
th.App.SetLicense(license)
// Try again, should succeed
received, resp = th.SystemAdminClient.PatchRole(role.Id, patch)
@@ -205,4 +208,25 @@ func TestPatchRole(t *testing.T) {
assert.Equal(t, received.Description, role.Description)
assert.EqualValues(t, received.Permissions, []string{"manage_system", "manage_incoming_webhooks", "manage_outgoing_webhooks"})
assert.Equal(t, received.SchemeManaged, role.SchemeManaged)
t.Run("Check guest permissions editing without E20 license", func(t *testing.T) {
license := model.NewTestLicense()
license.Features.GuestAccountsPermissions = model.NewBool(false)
th.App.SetLicense(license)
guestRole, err := th.App.Srv.Store.Role().GetByName("system_guest")
require.Nil(t, err)
received, resp = th.SystemAdminClient.PatchRole(guestRole.Id, patch)
CheckNotImplementedStatus(t, resp)
})
t.Run("Check guest permissions editing with E20 license", func(t *testing.T) {
license := model.NewTestLicense()
license.Features.GuestAccountsPermissions = model.NewBool(true)
th.App.SetLicense(license)
guestRole, err := th.App.Srv.Store.Role().GetByName("system_guest")
require.Nil(t, err)
_, resp = th.SystemAdminClient.PatchRole(guestRole.Id, patch)
CheckNoError(t, resp)
})
}

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

@@ -66,6 +66,7 @@ func (api *API) InitTeam() {
api.BaseRoutes.TeamMember.Handle("/schemeRoles", api.ApiSessionRequired(updateTeamMemberSchemeRoles)).Methods("PUT")
api.BaseRoutes.Team.Handle("/import", api.ApiSessionRequired(importTeam)).Methods("POST")
api.BaseRoutes.Team.Handle("/invite/email", api.ApiSessionRequired(inviteUsersToTeam)).Methods("POST")
api.BaseRoutes.Team.Handle("/invite-guests/email", api.ApiSessionRequired(inviteGuestsToChannels)).Methods("POST")
api.BaseRoutes.Teams.Handle("/invites/email", api.ApiSessionRequired(invalidateAllEmailInvites)).Methods("DELETE")
api.BaseRoutes.Teams.Handle("/invite/{invite_id:[A-Za-z0-9]+}", api.ApiHandler(getInviteInfo)).Methods("GET")
@@ -913,6 +914,42 @@ func inviteUsersToTeam(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
func inviteGuestsToChannels(c *Context, w http.ResponseWriter, r *http.Request) {
if c.App.License() == nil {
c.Err = model.NewAppError("Api4.InviteGuestsToChannels", "api.team.invate_guests_to_channels.license.error", nil, "", http.StatusNotImplemented)
return
}
if !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("Api4.InviteGuestsToChannels", "api.team.invate_guests_to_channels.disabled.error", nil, "", http.StatusNotImplemented)
return
}
c.RequireTeamId()
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionToTeam(c.App.Session, c.Params.TeamId, model.PERMISSION_INVITE_GUEST) {
c.SetPermissionError(model.PERMISSION_INVITE_GUEST)
return
}
guestsInvite := model.GuestsInviteFromJson(r.Body)
if err := guestsInvite.IsValid(); err != nil {
c.Err = err
return
}
err := c.App.InviteGuestsToChannels(c.Params.TeamId, guestsInvite, c.App.Session.UserId)
if err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}
func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireInviteId()
if c.Err != nil {

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

@@ -2290,6 +2290,136 @@ func TestInviteUsersToTeam(t *testing.T) {
})
}
func TestInviteGuestsToTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
guest1 := th.GenerateTestEmail()
guest2 := th.GenerateTestEmail()
emailList := []string{guest1, guest2}
//Delete all the messages before check the sample email
mailservice.DeleteMailBox(guest1)
mailservice.DeleteMailBox(guest2)
enableEmailInvitations := *th.App.Config().ServiceSettings.EnableEmailInvitations
restrictCreationToDomains := th.App.Config().TeamSettings.RestrictCreationToDomains
enableGuestAccounts := *th.App.Config().GuestAccountsSettings.Enable
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableEmailInvitations = &enableEmailInvitations })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = restrictCreationToDomains })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.Enable = &enableGuestAccounts })
}()
th.App.SetLicense(model.NewTestLicense(""))
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = false })
_, resp := th.SystemAdminClient.InviteGuestsToTeam(th.BasicTeam.Id, emailList, []string{th.BasicChannel.Id}, "test-message")
assert.NotNil(t, resp.Error, "Should be disabled")
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableEmailInvitations = false })
_, resp = th.SystemAdminClient.InviteGuestsToTeam(th.BasicTeam.Id, emailList, []string{th.BasicChannel.Id}, "test-message")
if resp.Error == nil {
t.Fatal("Should be disabled")
}
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableEmailInvitations = true })
th.App.SetLicense(nil)
_, resp = th.SystemAdminClient.InviteGuestsToTeam(th.BasicTeam.Id, emailList, []string{th.BasicChannel.Id}, "test-message")
if resp.Error == nil {
t.Fatal("Should be disabled")
}
th.App.SetLicense(model.NewTestLicense(""))
defer th.App.SetLicense(nil)
okMsg, resp := th.SystemAdminClient.InviteGuestsToTeam(th.BasicTeam.Id, emailList, []string{th.BasicChannel.Id}, "test-message")
CheckNoError(t, resp)
if !okMsg {
t.Fatal("should return true")
}
nameFormat := *th.App.Config().TeamSettings.TeammateNameDisplay
expectedSubject := utils.T("api.templates.invite_guest_subject",
map[string]interface{}{"SenderName": th.SystemAdminUser.GetDisplayName(nameFormat),
"TeamDisplayName": th.BasicTeam.DisplayName,
"SiteName": th.App.ClientConfig()["SiteName"]})
//Check if the email was send to the rigth email address
for _, email := range emailList {
var resultsMailbox mailservice.JSONMessageHeaderInbucket
err := mailservice.RetryInbucket(5, func() error {
var err error
resultsMailbox, err = mailservice.GetMailBox(email)
return err
})
if err != nil {
t.Log(err)
t.Log("No email was received, maybe due load on the server. Disabling this verification")
}
if err == nil && len(resultsMailbox) > 0 {
if !strings.ContainsAny(resultsMailbox[len(resultsMailbox)-1].To[0], email) {
t.Fatal("Wrong To recipient")
} else {
if resultsEmail, err := mailservice.GetMessageFromMailbox(email, resultsMailbox[len(resultsMailbox)-1].ID); err == nil {
if resultsEmail.Subject != expectedSubject {
t.Log(resultsEmail.Subject)
t.Log(expectedSubject)
t.Fatal("Wrong Subject")
}
}
}
}
}
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictCreationToDomains = "@global.com,@common.com" })
t.Run("restricted domains", func(t *testing.T) {
err := th.App.InviteGuestsToChannels(th.BasicTeam.Id, &model.GuestsInvite{Emails: emailList, Channels: []string{th.BasicChannel.Id}, Message: "test message"}, th.BasicUser.Id)
if err == nil {
t.Fatal("Adding users with non-restricted domains was allowed")
}
if err.Where != "InviteGuestsToChannels" || err.Id != "api.team.invite_members.invalid_email.app_error" {
t.Log(err)
t.Fatal("Got wrong error message!")
}
})
t.Run("override restricted domains", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "invalid.com,common.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err == nil {
t.Fatal("Should not update the team")
}
th.BasicTeam.AllowedDomains = "common.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil {
t.Log(err)
t.Fatal("Should update the team")
}
if err := th.App.InviteGuestsToChannels(th.BasicTeam.Id, &model.GuestsInvite{Emails: []string{"test@global.com"}, Channels: []string{th.BasicChannel.Id}, Message: "test message"}, th.BasicUser.Id); err == nil || err.Where != "InviteGuestsToChannels" {
t.Log(err)
t.Fatal("Per team restriction should take precedence over the global restriction")
}
if err := th.App.InviteGuestsToChannels(th.BasicTeam.Id, &model.GuestsInvite{Emails: []string{"test@common.com"}, Channels: []string{th.BasicChannel.Id}, Message: "test message"}, th.BasicUser.Id); err != nil {
t.Log(err)
t.Fatal("Failed to invite user which was common between team and global domain restriction")
}
if err := th.App.InviteGuestsToChannels(th.BasicTeam.Id, &model.GuestsInvite{Emails: []string{"test@invalid.com"}, Channels: []string{th.BasicChannel.Id}, Message: "test message"}, th.BasicUser.Id); err == nil {
t.Log(err)
t.Fatal("Should not invite user")
}
})
}
func TestGetTeamInviteInfo(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()

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

@@ -40,6 +40,8 @@ func (api *API) InitUser() {
api.BaseRoutes.User.Handle("/roles", api.ApiSessionRequired(updateUserRoles)).Methods("PUT")
api.BaseRoutes.User.Handle("/active", api.ApiSessionRequired(updateUserActive)).Methods("PUT")
api.BaseRoutes.User.Handle("/password", api.ApiSessionRequired(updatePassword)).Methods("PUT")
api.BaseRoutes.User.Handle("/promote", api.ApiSessionRequired(promoteGuestToUser)).Methods("POST")
api.BaseRoutes.User.Handle("/demote", api.ApiSessionRequired(demoteUserToGuest)).Methods("POST")
api.BaseRoutes.Users.Handle("/password/reset", api.ApiHandler(resetPassword)).Methods("POST")
api.BaseRoutes.Users.Handle("/password/reset/send", api.ApiHandler(sendPasswordReset)).Methods("POST")
api.BaseRoutes.Users.Handle("/email/verify", api.ApiHandler(verifyUserEmail)).Methods("POST")
@@ -92,7 +94,24 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
var ruser *model.User
var err *model.AppError
if len(tokenId) > 0 {
ruser, err = c.App.CreateUserWithToken(user, tokenId)
var token *model.Token
token, err = c.App.Srv.Store.Token().GetByToken(tokenId)
if err != nil {
c.Err = model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_invalid.app_error", nil, err.Error(), http.StatusBadRequest)
return
}
if token.Type == app.TOKEN_TYPE_GUEST_INVITATION {
if c.App.License() == nil {
c.Err = model.NewAppError("CreateUserWithToken", "api.user.create_user.guest_accounts.license.app_error", nil, "", http.StatusBadRequest)
return
}
if !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("CreateUserWithToken", "api.user.create_user.guest_accounts.disabled.app_error", nil, "", http.StatusBadRequest)
return
}
}
ruser, err = c.App.CreateUserWithToken(user, token)
} else if len(inviteId) > 0 {
ruser, err = c.App.CreateUserWithInviteId(user, inviteId)
} else if c.IsSystemAdmin() {
@@ -1372,6 +1391,17 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if user.IsGuest() {
if c.App.License() == nil {
c.Err = model.NewAppError("login", "api.user.login.guest_accounts.license.error", nil, "", http.StatusUnauthorized)
return
}
if !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("login", "api.user.login.guest_accounts.disabled.error", nil, "", http.StatusUnauthorized)
return
}
}
c.LogAuditWithUserId(user.Id, "authenticated")
session, err := c.App.DoLogin(w, r, user, deviceId)
@@ -1941,3 +1971,83 @@ func getUserTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
}
w.Write([]byte(result.ToJson()))
}
func promoteGuestToUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId()
if c.Err != nil {
return
}
if c.App.License() == nil {
c.Err = model.NewAppError("Api4.promoteGuestToUser", "api.team.promote_guest_to_user.license.error", nil, "", http.StatusNotImplemented)
return
}
if !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("Api4.promoteGuestToUser", "api.team.promote_guest_to_user.disabled.error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_PROMOTE_GUEST) {
c.SetPermissionError(model.PERMISSION_PROMOTE_GUEST)
return
}
user, err := c.App.GetUser(c.Params.UserId)
if err != nil {
c.Err = err
return
}
if !user.IsGuest() {
c.Err = model.NewAppError("Api4.promoteGuestToUser", "api.user.promote_guest_to_user.no_guest.app_error", nil, "", http.StatusNotImplemented)
return
}
if err := c.App.PromoteGuestToUser(user, c.App.Session.UserId); err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}
func demoteUserToGuest(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId()
if c.Err != nil {
return
}
if c.App.License() == nil {
c.Err = model.NewAppError("Api4.demoteUserToGuest", "api.team.demote_user_to_guest.license.error", nil, "", http.StatusNotImplemented)
return
}
if !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("Api4.demoteUserToGuest", "api.team.demote_user_to_guest.disabled.error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_DEMOTE_TO_GUEST) {
c.SetPermissionError(model.PERMISSION_DEMOTE_TO_GUEST)
return
}
user, err := c.App.GetUser(c.Params.UserId)
if err != nil {
c.Err = err
return
}
if user.IsGuest() {
c.Err = model.NewAppError("Api4.demoteUserToGuest", "api.user.demote_user_to_guest.already_guest.app_error", nil, "", http.StatusNotImplemented)
return
}
if err := c.App.DemoteUserToGuest(user); err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}

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

@@ -158,6 +158,9 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
model.PERMISSION_EDIT_OTHERS_POSTS.Id,
model.PERMISSION_MANAGE_OAUTH.Id,
model.PERMISSION_INVITE_USER.Id,
model.PERMISSION_INVITE_GUEST.Id,
model.PERMISSION_PROMOTE_GUEST.Id,
model.PERMISSION_DEMOTE_TO_GUEST.Id,
model.PERMISSION_DELETE_POST.Id,
model.PERMISSION_DELETE_OTHERS_POSTS.Id,
model.PERMISSION_CREATE_TEAM.Id,
@@ -340,6 +343,9 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
model.PERMISSION_EDIT_OTHERS_POSTS.Id,
model.PERMISSION_MANAGE_OAUTH.Id,
model.PERMISSION_INVITE_USER.Id,
model.PERMISSION_INVITE_GUEST.Id,
model.PERMISSION_PROMOTE_GUEST.Id,
model.PERMISSION_DEMOTE_TO_GUEST.Id,
model.PERMISSION_DELETE_POST.Id,
model.PERMISSION_DELETE_OTHERS_POSTS.Id,
model.PERMISSION_CREATE_TEAM.Id,
@@ -484,6 +490,9 @@ func TestDoEmojisPermissionsMigration(t *testing.T) {
model.PERMISSION_EDIT_OTHERS_POSTS.Id,
model.PERMISSION_MANAGE_OAUTH.Id,
model.PERMISSION_INVITE_USER.Id,
model.PERMISSION_INVITE_GUEST.Id,
model.PERMISSION_PROMOTE_GUEST.Id,
model.PERMISSION_DEMOTE_TO_GUEST.Id,
model.PERMISSION_DELETE_POST.Id,
model.PERMISSION_DELETE_OTHERS_POSTS.Id,
model.PERMISSION_CREATE_TEAM.Id,

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

@@ -1317,6 +1317,10 @@ func (a *App) GetChannelMemberCount(channelId string) (int64, *model.AppError) {
return a.Srv.Store.Channel().GetMemberCount(channelId, true)
}
func (a *App) GetChannelGuestCount(channelId string) (int64, *model.AppError) {
return a.Srv.Store.Channel().GetGuestCount(channelId, true)
}
func (a *App) GetChannelCounts(teamId string, userId string) (*model.ChannelCounts, *model.AppError) {
return a.Srv.Store.Channel().GetChannelCounts(teamId, userId)
}
@@ -1577,8 +1581,16 @@ func (a *App) postRemoveFromChannelMessage(removerUserId string, removedUser *mo
}
func (a *App) removeUserFromChannel(userIdToRemove string, removerUserId string, channel *model.Channel) *model.AppError {
user, err := a.Srv.Store.User().Get(userIdToRemove)
if err != nil {
return err
}
isGuest := user.IsGuest()
if channel.Name == model.DEFAULT_CHANNEL {
return model.NewAppError("RemoveUserFromChannel", "api.channel.remove.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "", http.StatusBadRequest)
if !isGuest {
return model.NewAppError("RemoveUserFromChannel", "api.channel.remove.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "", http.StatusBadRequest)
}
}
if channel.IsGroupConstrained() && userIdToRemove != removerUserId {
@@ -1603,6 +1615,23 @@ func (a *App) removeUserFromChannel(userIdToRemove string, removerUserId string,
return err
}
if isGuest {
currentMembers, err := a.GetChannelMembersForUser(channel.TeamId, userIdToRemove)
if err != nil {
return err
}
if len(*currentMembers) == 0 {
teamMember, err := a.GetTeamMember(channel.TeamId, userIdToRemove)
if err != nil {
return model.NewAppError("removeUserFromChannel", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest)
}
if err = a.RemoveTeamMemberFromTeam(teamMember, removerUserId); err != nil {
return err
}
}
}
a.InvalidateCacheForUser(userIdToRemove)
a.InvalidateCacheForChannelMembers(channel.Id)

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

@@ -437,6 +437,63 @@ func TestAddUserToChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
assert.NotNil(t, histories[0].LeaveTime)
}*/
func TestLeaveDefaultChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
guest := th.CreateGuest()
th.LinkUserToTeam(guest, th.BasicTeam)
townSquare, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, false)
require.Nil(t, err)
th.AddUserToChannel(guest, townSquare)
th.AddUserToChannel(th.BasicUser, townSquare)
t.Run("User tries to leave the default channel", func(t *testing.T) {
err = th.App.LeaveChannel(townSquare.Id, th.BasicUser.Id)
assert.NotNil(t, err, "It should fail to remove a regular user from the default channel")
assert.Equal(t, err.Id, "api.channel.remove.default.app_error")
_, err = th.App.GetChannelMember(townSquare.Id, th.BasicUser.Id)
assert.Nil(t, err)
})
t.Run("Guest leaves the default channel", func(t *testing.T) {
err = th.App.LeaveChannel(townSquare.Id, guest.Id)
assert.Nil(t, err, "It should allow to remove a guest user from the default channel")
_, err = th.App.GetChannelMember(townSquare.Id, guest.Id)
assert.NotNil(t, err)
})
}
func TestLeaveLastChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
guest := th.CreateGuest()
th.LinkUserToTeam(guest, th.BasicTeam)
townSquare, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, false)
require.Nil(t, err)
th.AddUserToChannel(guest, townSquare)
th.AddUserToChannel(guest, th.BasicChannel)
t.Run("Guest leaves not last channel", func(t *testing.T) {
err = th.App.LeaveChannel(townSquare.Id, guest.Id)
require.Nil(t, err)
_, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err, "It should maintain the team membership")
})
t.Run("Guest leaves last channel", func(t *testing.T) {
err = th.App.LeaveChannel(th.BasicChannel.Id, guest.Id)
assert.Nil(t, err, "It should allow to remove a guest user from the default channel")
_, err = th.App.GetChannelMember(th.BasicChannel.Id, guest.Id)
assert.NotNil(t, err)
_, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err, "It should remove the team membership")
})
}
func TestAddChannelMemberNoUserRequestor(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()

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

@@ -38,18 +38,41 @@ func (me *LeaveProvider) DoCommand(a *App, args *model.CommandArgs, message stri
if channel, noChannelErr = a.GetChannel(args.ChannelId); noChannelErr != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if channel.Name == model.DEFAULT_CHANNEL {
return &model.CommandResponse{Text: args.T("api.channel.leave.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
err := a.LeaveChannel(args.ChannelId, args.UserId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
team, err := a.GetTeam(args.TeamId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
err = a.LeaveChannel(args.ChannelId, args.UserId)
if err != nil {
if channel.Name == model.DEFAULT_CHANNEL {
return &model.CommandResponse{Text: args.T("api.channel.leave.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
member, err := a.GetTeamMember(team.Id, args.UserId)
if err != nil || member.DeleteAt != 0 {
return &model.CommandResponse{GotoLocation: args.SiteURL + "/"}
}
user, err := a.GetUser(args.UserId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if user.IsGuest() {
members, err := a.GetChannelMembersForUser(team.Id, args.UserId)
if err != nil || len(*members) == 0 {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
channel, err := a.GetChannel((*members)[0].ChannelId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + channel.Name}
}
return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + model.DEFAULT_CHANNEL}
}

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

@@ -5,9 +5,9 @@ package app
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
)
@@ -34,53 +34,114 @@ func TestLeaveProviderDoCommand(t *testing.T) {
CreatorId: th.BasicUser.Id,
}, false)
defaultChannel, err := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false)
require.Nil(t, err)
guest := th.CreateGuest()
th.App.AddUserToTeam(th.BasicTeam.Id, th.BasicUser.Id, th.BasicUser.Id)
th.App.AddUserToChannel(th.BasicUser, publicChannel)
th.App.AddUserToChannel(th.BasicUser, privateChannel)
th.App.AddUserToTeam(th.BasicTeam.Id, guest.Id, guest.Id)
th.App.AddUserToChannel(guest, publicChannel)
th.App.AddUserToChannel(guest, defaultChannel)
args := &model.CommandArgs{
T: func(s string, args ...interface{}) string { return s },
}
t.Run("Should error when no Channel ID in args", func(t *testing.T) {
args := &model.CommandArgs{
UserId: th.BasicUser.Id,
T: func(s string, args ...interface{}) string { return s },
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
})
// Should error when no Channel ID in args
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
t.Run("Should error when no Team ID in args", func(t *testing.T) {
args := &model.CommandArgs{
UserId: th.BasicUser.Id,
ChannelId: publicChannel.Id,
T: func(s string, args ...interface{}) string { return s },
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
})
// Should error when no Team ID in args
args.ChannelId = publicChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
t.Run("Leave a public channel", func(t *testing.T) {
args := &model.CommandArgs{
UserId: th.BasicUser.Id,
ChannelId: publicChannel.Id,
T: func(s string, args ...interface{}) string { return s },
TeamId: th.BasicTeam.Id,
SiteURL: "http://localhost:8065",
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
assert.Equal(t, args.SiteURL+"/"+th.BasicTeam.Name+"/channels/"+model.DEFAULT_CHANNEL, actual.GotoLocation)
assert.Equal(t, "", actual.ResponseType)
// Leave a public channel
siteURL := "http://localhost:8065"
args.TeamId = th.BasicTeam.Id
args.SiteURL = siteURL
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
assert.Equal(t, siteURL+"/"+th.BasicTeam.Name+"/channels/"+model.DEFAULT_CHANNEL, actual.GotoLocation)
assert.Equal(t, "", actual.ResponseType)
_, err = th.App.GetChannelMember(publicChannel.Id, th.BasicUser.Id)
assert.NotNil(t, err)
assert.NotNil(t, err.Id, "store.sql_channel.get_member.missing.app_error")
})
time.Sleep(100 * time.Millisecond)
t.Run("Leave a private channel", func(t *testing.T) {
args := &model.CommandArgs{
UserId: th.BasicUser.Id,
ChannelId: privateChannel.Id,
T: func(s string, args ...interface{}) string { return s },
TeamId: th.BasicTeam.Id,
SiteURL: "http://localhost:8065",
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
})
member, err := th.App.GetChannelMember(publicChannel.Id, th.BasicUser.Id)
if member == nil {
t.Errorf("Expected member object, got nil")
}
t.Run("Should not leave a default channel", func(t *testing.T) {
args := &model.CommandArgs{
UserId: th.BasicUser.Id,
ChannelId: defaultChannel.Id,
T: func(s string, args ...interface{}) string { return s },
TeamId: th.BasicTeam.Id,
SiteURL: "http://localhost:8065",
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.channel.leave.default.app_error", actual.Text)
})
if err != nil {
t.Errorf("Expected nil object, got %s", err)
}
t.Run("Should allow to leave a default channel if user is guest", func(t *testing.T) {
args := &model.CommandArgs{
UserId: guest.Id,
ChannelId: defaultChannel.Id,
T: func(s string, args ...interface{}) string { return s },
TeamId: th.BasicTeam.Id,
SiteURL: "http://localhost:8065",
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
assert.Equal(t, args.SiteURL+"/"+th.BasicTeam.Name+"/channels/"+publicChannel.Name, actual.GotoLocation)
assert.Equal(t, "", actual.ResponseType)
// Leave a private channel
args.ChannelId = privateChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
_, err = th.App.GetChannelMember(defaultChannel.Id, guest.Id)
assert.NotNil(t, err)
assert.NotNil(t, err.Id, "store.sql_channel.get_member.missing.app_error")
})
// Should not leave a default channel
defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false)
args.ChannelId = defaultChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.channel.leave.default.app_error", actual.Text)
t.Run("Should redirect to the team if is the last channel", func(t *testing.T) {
args := &model.CommandArgs{
UserId: guest.Id,
ChannelId: publicChannel.Id,
T: func(s string, args ...interface{}) string { return s },
TeamId: th.BasicTeam.Id,
SiteURL: "http://localhost:8065",
}
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
assert.Equal(t, args.SiteURL+"/", actual.GotoLocation)
assert.Equal(t, "", actual.ResponseType)
_, err = th.App.GetChannelMember(publicChannel.Id, guest.Id)
assert.NotNil(t, err)
assert.NotNil(t, err.Id, "store.sql_channel.get_member.missing.app_error")
})
}

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

@@ -7,6 +7,7 @@ import (
"fmt"
"net/url"
"path"
"strings"
"net/http"
@@ -357,6 +358,87 @@ func (a *App) SendInviteEmails(team *model.Team, senderName string, senderUserId
}
}
func (a *App) SendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, invites []string, siteURL string, message string) {
if a.Srv.EmailRateLimiter == nil {
a.Log.Error("Email invite not sent, rate limiting could not be setup.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id))
return
}
rateLimited, result, err := a.Srv.EmailRateLimiter.RateLimit(senderUserId, len(invites))
if err != nil {
a.Log.Error("Error rate limiting invite email.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(err))
return
}
if rateLimited {
a.Log.Error("Invite emails rate limited.",
mlog.String("user_id", senderUserId),
mlog.String("team_id", team.Id),
mlog.String("retry_after", result.RetryAfter.String()),
mlog.Err(err))
return
}
for _, invite := range invites {
if len(invite) > 0 {
senderRole := utils.T("api.team.invite_members.member")
subject := utils.T("api.templates.invite_guest_subject",
map[string]interface{}{"SenderName": senderName,
"TeamDisplayName": team.DisplayName,
"SiteName": a.ClientConfig()["SiteName"]})
bodyPage := a.NewEmailTemplate("invite_body", model.DEFAULT_LOCALE)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = utils.T("api.templates.invite_body.title")
bodyPage.Html["Info"] = utils.TranslateAsHtml(utils.T, "api.templates.invite_body_guest.info",
map[string]interface{}{"SenderStatus": senderRole, "SenderName": senderName, "TeamDisplayName": team.DisplayName})
bodyPage.Props["Button"] = utils.T("api.templates.invite_body.button")
bodyPage.Props["Message"] = ""
if message != "" {
bodyPage.Props["Message"] = message
}
bodyPage.Html["ExtraInfo"] = utils.TranslateAsHtml(utils.T, "api.templates.invite_body.extra_info",
map[string]interface{}{"TeamDisplayName": team.DisplayName})
bodyPage.Props["TeamURL"] = siteURL + "/" + team.Name
channelIds := []string{}
for _, channel := range channels {
channelIds = append(channelIds, channel.Id)
}
token := model.NewToken(
TOKEN_TYPE_GUEST_INVITATION,
model.MapToJson(map[string]string{
"teamId": team.Id,
"channels": strings.Join(channelIds, " "),
"email": invite,
"guest": "true",
}),
)
props := make(map[string]string)
props["email"] = invite
props["display_name"] = team.DisplayName
props["name"] = team.Name
data := model.MapToJson(props)
if err := a.Srv.Store.Token().Save(token); err != nil {
mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", err))
continue
}
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(data), url.QueryEscape(token.Token))
if !*a.Config().EmailSettings.SendEmailNotifications {
mlog.Info(fmt.Sprintf("sending invitation to %v %v", invite, bodyPage.Props["Link"]))
}
if err := a.SendMail(invite, subject, bodyPage.Render()); err != nil {
mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", err))
}
}
}
}
func (a *App) NewEmailTemplate(name, locale string) *utils.HTMLTemplate {
t := utils.NewHTMLTemplate(a.HTMLTemplates(), name)

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

@@ -149,6 +149,11 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
session.AddProp(model.SESSION_PROP_PLATFORM, plat)
session.AddProp(model.SESSION_PROP_OS, os)
session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion))
if user.IsGuest() {
session.AddProp(model.SESSION_PROP_IS_GUEST, "true")
} else {
session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
}
var err *model.AppError
if session, err = a.CreateSession(session); err != nil {

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

@@ -23,6 +23,7 @@ const (
MIGRATION_KEY_APPLY_CHANNEL_MANAGE_DELETE_TO_CHANNEL_USER = "apply_channel_manage_delete_to_channel_user"
MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER = "remove_channel_manage_delete_from_team_user"
MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION = "view_members_new_permission"
MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS = "add_manage_guests_permissions"
PERMISSION_MANAGE_SYSTEM = "manage_system"
PERMISSION_MANAGE_EMOJIS = "manage_emojis"
@@ -51,6 +52,10 @@ const (
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = "manage_public_channel_properties"
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = "manage_private_channel_properties"
PERMISSION_VIEW_MEMBERS = "view_members"
PERMISSION_INVITE_USER = "invite_user"
PERMISSION_INVITE_GUEST = "invite_guest"
PERMISSION_PROMOTE_GUEST = "promote_guest"
PERMISSION_DEMOTE_TO_GUEST = "demote_to_guest"
)
func isRole(role string) func(string, map[string]map[string]bool) bool {
@@ -273,6 +278,19 @@ func getViewMembersPermissionMigration() permissionsMap {
}
}
func getAddManageGuestsPermissionsMigration() permissionsMap {
return permissionsMap{
permissionTransformation{
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
Add: []string{PERMISSION_PROMOTE_GUEST, PERMISSION_DEMOTE_TO_GUEST},
},
permissionTransformation{
On: permissionExists(PERMISSION_INVITE_USER),
Add: []string{PERMISSION_INVITE_GUEST},
},
}
}
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
func (a *App) DoPermissionsMigrations() *model.AppError {
PermissionsMigrations := []struct {
@@ -287,6 +305,7 @@ func (a *App) DoPermissionsMigrations() *model.AppError {
{Key: MIGRATION_KEY_APPLY_CHANNEL_MANAGE_DELETE_TO_CHANNEL_USER, Migration: applyChannelManageDeleteToChannelUser},
{Key: MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER, Migration: removeChannelManageDeleteFromTeamUser},
{Key: MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION, Migration: getViewMembersPermissionMigration},
{Key: MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS, Migration: getAddManageGuestsPermissionsMigration},
}
for _, migration := range PermissionsMigrations {

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

@@ -342,7 +342,11 @@ func (api *PluginAPI) GetChannelStats(channelId string) (*model.ChannelStats, *m
if err != nil {
return nil, err
}
return &model.ChannelStats{ChannelId: channelId, MemberCount: memberCount}, nil
guestCount, err := api.app.GetChannelMemberCount(channelId)
if err != nil {
return nil, err
}
return &model.ChannelStats{ChannelId: channelId, MemberCount: memberCount, GuestCount: guestCount}, nil
}
func (api *PluginAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) {

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

@@ -93,6 +93,27 @@ func (a *App) GetSessions(userId string) ([]*model.Session, *model.AppError) {
return a.Srv.Store.Session().GetSessions(userId)
}
func (a *App) UpdateSessionsIsGuest(userId string, isGuest bool) {
sessions, err := a.Srv.Store.Session().GetSessions(userId)
if err != nil {
mlog.Error(fmt.Sprintf("Unable to get user sessions: userId=%s err=%s", userId, err.Error()))
}
for _, session := range sessions {
if isGuest {
session.AddProp(model.SESSION_PROP_IS_GUEST, "true")
} else {
session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
}
err := a.Srv.Store.Session().UpdateProps(session)
if err != nil {
mlog.Error(fmt.Sprintf("Unable to update isGuest session: %s", err.Error()))
continue
}
a.AddSessionToCache(session)
}
}
func (a *App) RevokeAllSessions(userId string) *model.AppError {
sessions, err := a.Srv.Store.Session().GetSessions(userId)
if err != nil {
@@ -329,6 +350,11 @@ func (a *App) createSessionForUserAccessToken(tokenString string) (*model.Sessio
if user.IsBot {
session.AddProp(model.SESSION_PROP_IS_BOT, model.SESSION_PROP_IS_BOT_VALUE)
}
if user.IsGuest() {
session.AddProp(model.SESSION_PROP_IS_GUEST, "true")
} else {
session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
}
session.SetExpireInDays(model.SESSION_USER_ACCESS_TOKEN_EXPIRY)
session, err = a.Srv.Store.Session().Save(session)

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

@@ -123,3 +123,57 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) {
_, err = th.App.GetSession(session.Token)
assert.Nil(t, err)
}
func TestUpdateSessionOnPromoteDemote(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.SetLicense(model.NewTestLicense())
t.Run("Promote Guest to User updates the session", func(t *testing.T) {
guest := th.CreateGuest()
session, err := th.App.CreateSession(&model.Session{UserId: guest.Id, Props: model.StringMap{model.SESSION_PROP_IS_GUEST: "true"}})
require.Nil(t, err)
rsession, err := th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "true", rsession.Props[model.SESSION_PROP_IS_GUEST])
err = th.App.PromoteGuestToUser(guest, th.BasicUser.Id)
require.Nil(t, err)
rsession, err = th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "false", rsession.Props[model.SESSION_PROP_IS_GUEST])
th.App.ClearSessionCacheForUser(session.UserId)
rsession, err = th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "false", rsession.Props[model.SESSION_PROP_IS_GUEST])
})
t.Run("Demote User to Guest updates the session", func(t *testing.T) {
user := th.CreateUser()
session, err := th.App.CreateSession(&model.Session{UserId: user.Id, Props: model.StringMap{model.SESSION_PROP_IS_GUEST: "false"}})
require.Nil(t, err)
rsession, err := th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "false", rsession.Props[model.SESSION_PROP_IS_GUEST])
err = th.App.DemoteUserToGuest(user)
require.Nil(t, err)
rsession, err = th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "true", rsession.Props[model.SESSION_PROP_IS_GUEST])
th.App.ClearSessionCacheForUser(session.UserId)
rsession, err = th.App.GetSession(session.Token)
require.Nil(t, err)
assert.Equal(t, "true", rsession.Props[model.SESSION_PROP_IS_GUEST])
})
}

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

@@ -421,11 +421,11 @@ func (a *App) AddUserToTeamByToken(userId string, tokenId string) (*model.Team,
return nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_invalid.app_error", nil, err.Error(), http.StatusBadRequest)
}
if token.Type != TOKEN_TYPE_TEAM_INVITATION {
if token.Type != TOKEN_TYPE_TEAM_INVITATION && token.Type != TOKEN_TYPE_GUEST_INVITATION {
return nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest)
}
if model.GetMillis()-token.CreateAt >= TEAM_INVITATION_EXPIRY_TIME {
if model.GetMillis()-token.CreateAt >= INVITATION_EXPIRY_TIME {
a.DeleteToken(token)
return nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_expired.app_error", nil, "", http.StatusBadRequest)
}
@@ -462,10 +462,31 @@ func (a *App) AddUserToTeamByToken(userId string, tokenId string) (*model.Team,
}
user := result.Data.(*model.User)
if user.IsGuest() && token.Type == TOKEN_TYPE_TEAM_INVITATION {
return nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.invalid_invitation_type.app_error", nil, "", http.StatusBadRequest)
}
if !user.IsGuest() && token.Type == TOKEN_TYPE_GUEST_INVITATION {
return nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.invalid_invitation_type.app_error", nil, "", http.StatusBadRequest)
}
if err := a.JoinUserToTeam(team, user, ""); err != nil {
return nil, err
}
if token.Type == TOKEN_TYPE_GUEST_INVITATION {
channels, err := a.Srv.Store.Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "))
if err != nil {
return nil, err
}
for _, channel := range channels {
_, err := a.AddUserToChannel(user, channel)
if err != nil {
mlog.Error(err.Error())
}
}
}
if err := a.DeleteToken(token); err != nil {
return nil, err
}
@@ -589,9 +610,11 @@ func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId
shouldBeAdmin := team.Email == user.Email
// Soft error if there is an issue joining the default channels
if err := a.JoinDefaultChannels(team.Id, user, shouldBeAdmin, userRequestorId); err != nil {
mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id))
if !user.IsGuest() {
// Soft error if there is an issue joining the default channels
if err := a.JoinDefaultChannels(team.Id, user, shouldBeAdmin, userRequestorId); err != nil {
mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id))
}
}
a.ClearSessionCacheForUser(user.Id)
@@ -824,54 +847,18 @@ func (a *App) RemoveUserFromTeam(teamId string, userId string, requestorId strin
return nil
}
func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string) *model.AppError {
teamMember, err := a.GetTeamMember(team.Id, user.Id)
if err != nil {
return model.NewAppError("LeaveTeam", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest)
}
func (a *App) RemoveTeamMemberFromTeam(teamMember *model.TeamMember, requestorId string) *model.AppError {
// Send the websocket message before we actually do the remove so the user being removed gets it.
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LEAVE_TEAM, teamMember.TeamId, "", "", nil)
message.Add("user_id", teamMember.UserId)
message.Add("team_id", teamMember.TeamId)
a.Publish(message)
var channelList *model.ChannelList
if channelList, err = a.Srv.Store.Channel().GetChannels(team.Id, user.Id, true); err != nil {
if err.Id == "store.sql_channel.get_channels.not_found.app_error" {
channelList = &model.ChannelList{}
} else {
return err
}
}
for _, channel := range *channelList {
if !channel.IsGroupOrDirect() {
a.InvalidateCacheForChannelMembers(channel.Id)
if err = a.Srv.Store.Channel().RemoveMember(channel.Id, user.Id); err != nil {
return err
}
}
}
channel, err := a.Srv.Store.Channel().GetByName(team.Id, model.DEFAULT_CHANNEL, false)
user, err := a.Srv.Store.User().Get(teamMember.UserId)
if err != nil {
return err
}
if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages {
if requestorId == user.Id {
if err := a.postLeaveTeamMessage(user, channel); err != nil {
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
}
} else {
if err := a.postRemoveFromTeamMessage(user, channel); err != nil {
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
}
}
}
// Send the websocket message before we actually do the remove so the user being removed gets it.
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LEAVE_TEAM, team.Id, "", "", nil)
message.Add("user_id", user.Id)
message.Add("team_id", team.Id)
a.Publish(message)
teamMember.Roles = ""
teamMember.DeleteAt = model.GetMillis()
@@ -908,7 +895,7 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string)
}
// delete the preferences that set the last channel used in the team and other team specific preferences
if err := a.Srv.Store.Preference().DeleteCategory(user.Id, team.Id); err != nil {
if err := a.Srv.Store.Preference().DeleteCategory(user.Id, teamMember.TeamId); err != nil {
return err
}
@@ -919,6 +906,56 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string)
return nil
}
func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string) *model.AppError {
teamMember, err := a.GetTeamMember(team.Id, user.Id)
if err != nil {
return model.NewAppError("LeaveTeam", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest)
}
var channelList *model.ChannelList
if channelList, err = a.Srv.Store.Channel().GetChannels(team.Id, user.Id, true); err != nil {
if err.Id == "store.sql_channel.get_channels.not_found.app_error" {
channelList = &model.ChannelList{}
} else {
return err
}
}
for _, channel := range *channelList {
if !channel.IsGroupOrDirect() {
a.InvalidateCacheForChannelMembers(channel.Id)
if err = a.Srv.Store.Channel().RemoveMember(channel.Id, user.Id); err != nil {
return err
}
}
}
channel, err := a.Srv.Store.Channel().GetByName(team.Id, model.DEFAULT_CHANNEL, false)
if err != nil {
return err
}
if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages {
if requestorId == user.Id {
if err = a.postLeaveTeamMessage(user, channel); err != nil {
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
}
} else {
if err = a.postRemoveFromTeamMessage(user, channel); err != nil {
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
}
}
}
err = a.RemoveTeamMemberFromTeam(teamMember, requestorId)
if err != nil {
return err
}
return nil
}
func (a *App) postLeaveTeamMessage(user *model.User, channel *model.Channel) *model.AppError {
post := &model.Post{
ChannelId: channel.Id,
@@ -1011,6 +1048,77 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamId, senderId string)
return nil
}
func (a *App) InviteGuestsToChannels(teamId string, guestsInvite *model.GuestsInvite, senderId string) *model.AppError {
if !*a.Config().ServiceSettings.EnableEmailInvitations {
return model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented)
}
if err := guestsInvite.IsValid(); err != nil {
return err
}
tchan := make(chan store.StoreResult, 1)
go func() {
team, err := a.Srv.Store.Team().Get(teamId)
tchan <- store.StoreResult{Data: team, Err: err}
close(tchan)
}()
cchan := make(chan store.StoreResult, 1)
go func() {
channels, err := a.Srv.Store.Channel().GetChannelsByIds(guestsInvite.Channels)
cchan <- store.StoreResult{Data: channels, Err: err}
close(cchan)
}()
uchan := make(chan store.StoreResult, 1)
go func() {
user, err := a.Srv.Store.User().Get(senderId)
uchan <- store.StoreResult{Data: user, Err: err}
close(uchan)
}()
result := <-cchan
if result.Err != nil {
return result.Err
}
channels := result.Data.([]*model.Channel)
result = <-uchan
if result.Err != nil {
return result.Err
}
user := result.Data.(*model.User)
result = <-tchan
if result.Err != nil {
return result.Err
}
team := result.Data.(*model.Team)
for _, channel := range channels {
if channel.TeamId != teamId {
return model.NewAppError("InviteGuestsToChannels", "api.team.invite_guests.channel_in_invalid_team.app_error", nil, "", http.StatusBadRequest)
}
}
var invalidEmailList []string
for _, email := range guestsInvite.Emails {
if !a.isTeamEmailAddressAllowed(email, team.AllowedDomains) {
invalidEmailList = append(invalidEmailList, email)
}
}
if len(invalidEmailList) > 0 {
s := strings.Join(invalidEmailList, ", ")
err := model.NewAppError("InviteGuestsToChannels", "api.team.invite_members.invalid_email.app_error", map[string]interface{}{"Addresses": s}, "", http.StatusBadRequest)
return err
}
nameFormat := *a.Config().TeamSettings.TeammateNameDisplay
a.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, guestsInvite.Emails, a.GetSiteURL(), guestsInvite.Message)
return nil
}
func (a *App) FindTeamByName(name string) bool {
if _, err := a.Srv.Store.Team().GetByName(name); err != nil {
return false
@@ -1175,7 +1283,7 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) {
return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest)
}
if model.GetMillis()-token.CreateAt >= TEAM_INVITATION_EXPIRY_TIME {
if model.GetMillis()-token.CreateAt >= INVITATION_EXPIRY_TIME {
a.DeleteToken(token)
return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "", http.StatusBadRequest)
}

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

@@ -193,6 +193,7 @@ func TestAddUserToTeamByToken(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user)
rguest := th.CreateGuest()
t.Run("invalid token", func(t *testing.T) {
if _, err := th.App.AddUserToTeamByToken(ruser.Id, "123"); err == nil {
@@ -217,7 +218,7 @@ func TestAddUserToTeamByToken(t *testing.T) {
TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
)
token.CreateAt = model.GetMillis() - TEAM_INVITATION_EXPIRY_TIME - 1
token.CreateAt = model.GetMillis() - INVITATION_EXPIRY_TIME - 1
require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token)
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil {
@@ -261,6 +262,49 @@ func TestAddUserToTeamByToken(t *testing.T) {
}
_, err := th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, ruser.Id)
require.Nil(t, err)
assert.Len(t, *members, 2)
})
t.Run("invalid add a guest using a regular invite", func(t *testing.T) {
token := model.NewToken(
TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
_, err := th.App.AddUserToTeamByToken(rguest.Id, token.Token)
assert.NotNil(t, err)
})
t.Run("invalid add a regular user using a guest invite", func(t *testing.T) {
token := model.NewToken(
TOKEN_TYPE_GUEST_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
_, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token)
assert.NotNil(t, err)
})
t.Run("valid request from guest invite", func(t *testing.T) {
token := model.NewToken(
TOKEN_TYPE_GUEST_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
if _, err := th.App.AddUserToTeamByToken(rguest.Id, token.Token); err != nil {
t.Log(err)
t.Fatal("Should add user to the team")
}
_, err := th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, rguest.Id)
require.Nil(t, err)
require.Len(t, *members, 1)
assert.Equal(t, (*members)[0].ChannelId, th.BasicChannel.Id)
})
t.Run("group-constrained team", func(t *testing.T) {

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

@@ -40,26 +40,22 @@ const (
TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery"
TOKEN_TYPE_VERIFY_EMAIL = "verify_email"
TOKEN_TYPE_TEAM_INVITATION = "team_invitation"
TOKEN_TYPE_GUEST_INVITATION = "guest_invitation"
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
TEAM_INVITATION_EXPIRY_TIME = 1000 * 60 * 60 * 48 // 48 hours
INVITATION_EXPIRY_TIME = 1000 * 60 * 60 * 48 // 48 hours
IMAGE_PROFILE_PIXEL_DIMENSION = 128
)
func (a *App) CreateUserWithToken(user *model.User, tokenId string) (*model.User, *model.AppError) {
func (a *App) CreateUserWithToken(user *model.User, token *model.Token) (*model.User, *model.AppError) {
if err := a.IsUserSignUpAllowed(); err != nil {
return nil, err
}
token, err := a.Srv.Store.Token().GetByToken(tokenId)
if err != nil {
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_invalid.app_error", nil, err.Error(), http.StatusBadRequest)
}
if token.Type != TOKEN_TYPE_TEAM_INVITATION {
if token.Type != TOKEN_TYPE_TEAM_INVITATION && token.Type != TOKEN_TYPE_GUEST_INVITATION {
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest)
}
if model.GetMillis()-token.CreateAt >= TEAM_INVITATION_EXPIRY_TIME {
if model.GetMillis()-token.CreateAt >= INVITATION_EXPIRY_TIME {
a.DeleteToken(token)
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_expired.app_error", nil, "", http.StatusBadRequest)
}
@@ -71,10 +67,20 @@ func (a *App) CreateUserWithToken(user *model.User, tokenId string) (*model.User
return nil, err
}
channels, err := a.Srv.Store.Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "))
if err != nil {
return nil, err
}
user.Email = tokenData["email"]
user.EmailVerified = true
ruser, err := a.CreateUser(user)
var ruser *model.User
if token.Type == TOKEN_TYPE_TEAM_INVITATION {
ruser, err = a.CreateUser(user)
} else {
ruser, err = a.CreateGuest(user)
}
if err != nil {
return nil, err
}
@@ -85,6 +91,15 @@ func (a *App) CreateUserWithToken(user *model.User, tokenId string) (*model.User
a.AddDirectChannels(team.Id, ruser)
if token.Type == TOKEN_TYPE_GUEST_INVITATION {
for _, channel := range channels {
_, err := a.AddUserToChannel(ruser, channel)
if err != nil {
mlog.Error(err.Error())
}
}
}
if err := a.DeleteToken(token); err != nil {
return nil, err
}
@@ -233,15 +248,19 @@ func (a *App) CreateGuest(user *model.User) (*model.User, *model.AppError) {
}
func (a *App) createUserOrGuest(user *model.User, guest bool) (*model.User, *model.AppError) {
if !user.IsLDAPUser() && !user.IsSAMLUser() && !CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
return nil, model.NewAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
user.Roles = model.SYSTEM_USER_ROLE_ID
if guest {
user.Roles = model.SYSTEM_GUEST_ROLE_ID
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
return nil, model.NewAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !CheckUserDomain(user, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) {
return nil, model.NewAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
// Below is a special case where the first user in the entire
// system is granted the system_admin role
count, err := a.Srv.Store.User().Count(model.UserCountOptions{IncludeDeleted: true})
@@ -1056,13 +1075,13 @@ func (a *App) sendUpdatedUserEvent(user model.User) {
adminCopyOfUser := user.DeepCopy()
a.SanitizeProfile(adminCopyOfUser, true)
adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
adminMessage.Add("user", *adminCopyOfUser)
adminMessage.Add("user", &adminCopyOfUser)
adminMessage.Broadcast.ContainsSensitiveData = true
a.Publish(adminMessage)
a.SanitizeProfile(&user, false)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user)
message.Add("user", &user)
message.Broadcast.ContainsSanitizedData = true
a.Publish(message)
}
@@ -2200,6 +2219,61 @@ func (a *App) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *m
return listOfAllowedChannels, nil
}
// PromoteGuestToUser Convert user's roles and all his mermbership's roles from
// guest roles to regular user roles.
func (a *App) PromoteGuestToUser(user *model.User, requestorId string) *model.AppError {
err := a.Srv.Store.User().PromoteGuestToUser(user.Id)
if err != nil {
return err
}
userTeams, err := a.Srv.Store.Team().GetTeamsByUserId(user.Id)
if err != nil {
return err
}
for _, team := range userTeams {
// Soft error if there is an issue joining the default channels
if err = a.JoinDefaultChannels(team.Id, user, false, requestorId); err != nil {
mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.String("requestor_id", requestorId))
}
}
promotedUser, err := a.GetUser(user.Id)
if err != nil {
return err
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", promotedUser)
a.Publish(message)
a.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest())
return nil
}
// DemoteUserToGuest Convert user's roles and all his mermbership's roles from
// regular user roles to guest roles.
func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
err := a.Srv.Store.User().DemoteUserToGuest(user.Id)
if err != nil {
return err
}
demotedUser, err := a.GetUser(user.Id)
if err != nil {
return err
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", demotedUser)
a.Publish(message)
a.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest())
return nil
}
// invalidateUserCacheAndPublish Invalidates cache for a user and publishes user updated event
func (a *App) invalidateUserCacheAndPublish(userId string) {
a.InvalidateCacheForUser(userId)

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

@@ -561,7 +561,7 @@ func TestCreateUserWithToken(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
t.Run("invalid token", func(t *testing.T) {
if _, err := th.App.CreateUserWithToken(&user, "123"); err == nil {
if _, err := th.App.CreateUserWithToken(&user, &model.Token{Token: "123"}); err == nil {
t.Fatal("Should fail on unexisting token")
}
})
@@ -573,7 +573,7 @@ func TestCreateUserWithToken(t *testing.T) {
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token)
if _, err := th.App.CreateUserWithToken(&user, token.Token); err == nil {
if _, err := th.App.CreateUserWithToken(&user, token); err == nil {
t.Fatal("Should fail on bad token type")
}
})
@@ -583,10 +583,10 @@ func TestCreateUserWithToken(t *testing.T) {
TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
)
token.CreateAt = model.GetMillis() - TEAM_INVITATION_EXPIRY_TIME - 1
token.CreateAt = model.GetMillis() - INVITATION_EXPIRY_TIME - 1
require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token)
if _, err := th.App.CreateUserWithToken(&user, token.Token); err == nil {
if _, err := th.App.CreateUserWithToken(&user, token); err == nil {
t.Fatal("Should fail on expired token")
}
})
@@ -598,28 +598,60 @@ func TestCreateUserWithToken(t *testing.T) {
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token)
if _, err := th.App.CreateUserWithToken(&user, token.Token); err == nil {
if _, err := th.App.CreateUserWithToken(&user, token); err == nil {
t.Fatal("Should fail on bad team id")
}
})
t.Run("valid request", func(t *testing.T) {
t.Run("valid regular user request", func(t *testing.T) {
invitationEmail := model.NewId() + "other-email@test.com"
token := model.NewToken(
TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail}),
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
newUser, err := th.App.CreateUserWithToken(&user, token.Token)
newUser, err := th.App.CreateUserWithToken(&user, token)
if err != nil {
t.Log(err)
t.Fatal("Should add user to the team")
}
assert.False(t, newUser.IsGuest())
if newUser.Email != invitationEmail {
t.Fatal("The user email must be the invitation one")
}
_, err = th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, newUser.Id)
require.Nil(t, err)
assert.Len(t, *members, 2)
})
t.Run("valid guest request", func(t *testing.T) {
invitationEmail := model.NewId() + "other-email@test.com"
token := model.NewToken(
TOKEN_TYPE_GUEST_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id}),
)
require.Nil(t, th.App.Srv.Store.Token().Save(token))
guest := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
newGuest, err := th.App.CreateUserWithToken(&guest, token)
if err != nil {
t.Log(err)
t.Fatal("Should add user to the team")
}
assert.True(t, newGuest.IsGuest())
if newGuest.Email != invitationEmail {
t.Fatal("The user email must be the invitation one")
}
_, err = th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, newGuest.Id)
require.Nil(t, err)
require.Len(t, *members, 1)
assert.Equal(t, (*members)[0].ChannelId, th.BasicChannel.Id)
})
}
@@ -870,10 +902,6 @@ func TestGetViewUsersRestrictionsForTeam(t *testing.T) {
team2channel1 := th.CreateChannel(team2)
th.CreateChannel(team2) // Another channel
team2offtopic, err := th.App.GetChannelByName("off-topic", team2.Id, false)
require.Nil(t, err)
team2townsquare, err := th.App.GetChannelByName("town-square", team2.Id, false)
require.Nil(t, err)
th.App.AddUserToChannel(user1, team1channel1)
th.App.AddUserToChannel(user1, team1channel2)
@@ -929,6 +957,227 @@ func TestGetViewUsersRestrictionsForTeam(t *testing.T) {
require.Nil(t, err)
assert.NotNil(t, restrictions)
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions)
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id}, restrictions)
})
}
func TestPromoteGuestToUser(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
t.Run("Must fail with regular user", func(t *testing.T) {
require.Equal(t, "system_user", th.BasicUser.Roles)
err := th.App.PromoteGuestToUser(th.BasicUser, th.BasicUser.Id)
require.Nil(t, err)
user, err := th.App.GetUser(th.BasicUser.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", user.Roles)
})
t.Run("Must work with guest user without teams or channels", func(t *testing.T) {
guest := th.CreateGuest()
require.Equal(t, "system_guest", guest.Roles)
err := th.App.PromoteGuestToUser(guest, th.BasicUser.Id)
require.Nil(t, err)
guest, err = th.App.GetUser(guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
})
t.Run("Must work with guest user with teams but no channels", func(t *testing.T) {
guest := th.CreateGuest()
require.Equal(t, "system_guest", guest.Roles)
th.LinkUserToTeam(guest, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeGuest)
require.False(t, teamMember.SchemeUser)
err = th.App.PromoteGuestToUser(guest, th.BasicUser.Id)
require.Nil(t, err)
guest, err = th.App.GetUser(guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeGuest)
assert.True(t, teamMember.SchemeUser)
})
t.Run("Must work with guest user with teams and channels", func(t *testing.T) {
guest := th.CreateGuest()
require.Equal(t, "system_guest", guest.Roles)
th.LinkUserToTeam(guest, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeGuest)
require.False(t, teamMember.SchemeUser)
channelMember := th.AddUserToChannel(guest, th.BasicChannel)
require.True(t, channelMember.SchemeGuest)
require.False(t, channelMember.SchemeUser)
err = th.App.PromoteGuestToUser(guest, th.BasicUser.Id)
require.Nil(t, err)
guest, err = th.App.GetUser(guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeGuest)
assert.True(t, teamMember.SchemeUser)
channelMember, err = th.App.GetChannelMember(th.BasicChannel.Id, guest.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeGuest)
assert.True(t, teamMember.SchemeUser)
})
t.Run("Must add the default channels", func(t *testing.T) {
guest := th.CreateGuest()
require.Equal(t, "system_guest", guest.Roles)
th.LinkUserToTeam(guest, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeGuest)
require.False(t, teamMember.SchemeUser)
channelMember := th.AddUserToChannel(guest, th.BasicChannel)
require.True(t, channelMember.SchemeGuest)
require.False(t, channelMember.SchemeUser)
channelMembers, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, guest.Id)
require.Nil(t, err)
require.Len(t, *channelMembers, 1)
err = th.App.PromoteGuestToUser(guest, th.BasicUser.Id)
require.Nil(t, err)
guest, err = th.App.GetUser(guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_user", guest.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeGuest)
assert.True(t, teamMember.SchemeUser)
channelMember, err = th.App.GetChannelMember(th.BasicChannel.Id, guest.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeGuest)
assert.True(t, teamMember.SchemeUser)
channelMembers, err = th.App.GetChannelMembersForUser(th.BasicTeam.Id, guest.Id)
require.Nil(t, err)
assert.Len(t, *channelMembers, 3)
})
}
func TestDemoteUserToGuest(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
t.Run("Must fail with guest user", func(t *testing.T) {
guest := th.CreateGuest()
require.Equal(t, "system_guest", guest.Roles)
err := th.App.DemoteUserToGuest(guest)
require.Nil(t, err)
user, err := th.App.GetUser(guest.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
})
t.Run("Must work with user without teams or channels", func(t *testing.T) {
user := th.CreateUser()
require.Equal(t, "system_user", user.Roles)
err := th.App.DemoteUserToGuest(user)
require.Nil(t, err)
user, err = th.App.GetUser(user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
})
t.Run("Must work with user with teams but no channels", func(t *testing.T) {
user := th.CreateUser()
require.Equal(t, "system_user", user.Roles)
th.LinkUserToTeam(user, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeUser)
require.False(t, teamMember.SchemeGuest)
err = th.App.DemoteUserToGuest(user)
require.Nil(t, err)
user, err = th.App.GetUser(user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
})
t.Run("Must work with user user with teams and channels", func(t *testing.T) {
user := th.CreateUser()
require.Equal(t, "system_user", user.Roles)
th.LinkUserToTeam(user, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeUser)
require.False(t, teamMember.SchemeGuest)
channelMember := th.AddUserToChannel(user, th.BasicChannel)
require.True(t, channelMember.SchemeUser)
require.False(t, channelMember.SchemeGuest)
err = th.App.DemoteUserToGuest(user)
require.Nil(t, err)
user, err = th.App.GetUser(user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
channelMember, err = th.App.GetChannelMember(th.BasicChannel.Id, user.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
})
t.Run("Must respect the current channels not removing defaults", func(t *testing.T) {
user := th.CreateUser()
require.Equal(t, "system_user", user.Roles)
th.LinkUserToTeam(user, th.BasicTeam)
teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
require.Nil(t, err)
require.True(t, teamMember.SchemeUser)
require.False(t, teamMember.SchemeGuest)
channelMember := th.AddUserToChannel(user, th.BasicChannel)
require.True(t, channelMember.SchemeUser)
require.False(t, channelMember.SchemeGuest)
channelMembers, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, user.Id)
require.Nil(t, err)
require.Len(t, *channelMembers, 3)
err = th.App.DemoteUserToGuest(user)
require.Nil(t, err)
user, err = th.App.GetUser(user.Id)
assert.Nil(t, err)
assert.Equal(t, "system_guest", user.Roles)
teamMember, err = th.App.GetTeamMember(th.BasicTeam.Id, user.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
channelMember, err = th.App.GetChannelMember(th.BasicChannel.Id, user.Id)
assert.Nil(t, err)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
channelMembers, err = th.App.GetChannelMembersForUser(th.BasicTeam.Id, user.Id)
require.Nil(t, err)
assert.Len(t, *channelMembers, 3)
})
}

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

@@ -356,6 +356,15 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
}
if msg.Event == model.WEBSOCKET_EVENT_USER_UPDATED && webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" {
canSee, err := webCon.App.UserCanSeeOtherUser(webCon.UserId, msg.Data["user"].(*model.User).Id)
if err != nil {
mlog.Error("webhub.shouldSendEvent: " + err.Error())
return false
}
return canSee
}
return true
}

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

@@ -246,6 +246,7 @@ func (a *App) InvalidateCacheForChannelMembers(channelId string) {
func (a *App) InvalidateCacheForChannelMembersSkipClusterSend(channelId string) {
a.Srv.Store.User().InvalidateProfilesInChannelCache(channelId)
a.Srv.Store.Channel().InvalidateMemberCount(channelId)
a.Srv.Store.Channel().InvalidateGuestCount(channelId)
}
func (a *App) InvalidateCacheForChannelMembersNotifyProps(channelId string) {

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

@@ -273,6 +273,7 @@ func GenerateLimitedClientConfig(c *model.Config, diagnosticId string, license *
props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText
props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication)
props["EnforceMultifactorAuthentication"] = "false"
props["EnableGuestAccounts"] = strconv.FormatBool(*c.GuestAccountsSettings.Enable)
if license != nil {
if *license.Features.LDAP {

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

@@ -1862,6 +1862,14 @@
"id": "api.team.add_user_to_team.missing_parameter.app_error",
"translation": "Parameter required to add user to team."
},
{
"id": "api.team.demote_user_to_guest.disabled.error",
"translation": "Guest accounts are disabled."
},
{
"id": "api.team.demote_user_to_guest.license.error",
"translation": "Your license does not support guest accounts"
},
{
"id": "api.team.get_invite_info.not_open_team",
"translation": "Invite is invalid because this is not an open team."
@@ -1906,6 +1914,18 @@
"id": "api.team.invalidate_all_email_invites.app_error",
"translation": "Error invalidating email invites."
},
{
"id": "api.team.invate_guests_to_channels.disabled.error",
"translation": "Guest accounts are disabled"
},
{
"id": "api.team.invate_guests_to_channels.license.error",
"translation": "Your license does not support guest accounts"
},
{
"id": "api.team.invite_guests.channel_in_invalid_team.app_error",
"translation": "The channels of the invite must be part of the team of the invite."
},
{
"id": "api.team.invite_members.disabled.app_error",
"translation": "Email invitations are disabled."
@@ -1950,6 +1970,14 @@
"id": "api.team.move_channel.success",
"translation": "This channel has been moved to this team from %v."
},
{
"id": "api.team.promote_guest_to_user.disabled.error",
"translation": "Guest accounts are disabled."
},
{
"id": "api.team.promote_guest_to_user.license.error",
"translation": "Your license does not support guest accounts"
},
{
"id": "api.team.remove_member.group_constrained.app_error",
"translation": "Unable to remove a user from a group-constrained team."
@@ -2118,6 +2146,14 @@
"id": "api.templates.invite_body.title",
"translation": "You've been invited"
},
{
"id": "api.templates.invite_body_guest.info",
"translation": "The team {{.SenderStatus}} [[{{.SenderName}}]], has invited you to join team [[{{.TeamDisplayName}}]] as a guest."
},
{
"id": "api.templates.invite_guest_subject",
"translation": "[{{ .SiteName }}] {{ .SenderName }} invited you to join the team {{ .TeamDisplayName }} as a guest"
},
{
"id": "api.templates.invite_subject",
"translation": "[{{ .SiteName }}] {{ .SenderName }} invited you to join {{ .TeamDisplayName }} Team"
@@ -2366,6 +2402,18 @@
"id": "api.user.create_user.disabled.app_error",
"translation": "User creation is disabled."
},
{
"id": "api.user.create_user.guest_accounts.disabled.app_error",
"translation": "Guest accounts are disabled"
},
{
"id": "api.user.create_user.guest_accounts.license.app_error",
"translation": "Your license does not support guest accounts"
},
{
"id": "api.user.create_user.invalid_invitation_type.app_error",
"translation": "Unable to create the user, invalid invitation."
},
{
"id": "api.user.create_user.no_open_server",
"translation": "This server does not allow open signups. Please speak with your Administrator to receive an invitation."
@@ -2382,6 +2430,10 @@
"id": "api.user.create_user.signup_link_invalid.app_error",
"translation": "The signup link does not appear to be valid"
},
{
"id": "api.user.demote_user_to_guest.already_guest.app_error",
"translation": "Unable to convert the user to guest because is already a guest"
},
{
"id": "api.user.email_to_ldap.not_available.app_error",
"translation": "AD/LDAP not available on this server"
@@ -2422,6 +2474,14 @@
"id": "api.user.login.client_side_cert.license.app_error",
"translation": "Attempt to use the experimental feature ClientSideCertEnable without a valid enterprise license"
},
{
"id": "api.user.login.guest_accounts.disabled.error",
"translation": "Guest accounts are disabled"
},
{
"id": "api.user.login.guest_accounts.license.error",
"translation": "Your license does not support guest accounts"
},
{
"id": "api.user.login.inactive.app_error",
"translation": "Login failed because your account has been deactivated. Please contact an administrator."
@@ -2474,6 +2534,10 @@
"id": "api.user.oauth_to_email.not_available.app_error",
"translation": "Authentication Transfer not configured or available on this server."
},
{
"id": "api.user.promote_guest_to_user.no_guest.app_error",
"translation": "Unable to convert the guest to regular user because is not a guest"
},
{
"id": "api.user.reset_password.broken_token.app_error",
"translation": "The reset password token does not appear to be valid."
@@ -4802,6 +4866,22 @@
"id": "model.group_syncable.type.app_error",
"translation": "invalid type property for group syncable"
},
{
"id": "model.guest.is_valid.channel.app_error",
"translation": "Invalid channel"
},
{
"id": "model.guest.is_valid.channels.app_error",
"translation": "Invalid channels"
},
{
"id": "model.guest.is_valid.email.app_error",
"translation": "Invalid email"
},
{
"id": "model.guest.is_valid.emails.app_error",
"translation": "Invalid emails"
},
{
"id": "model.incoming_hook.channel_id.app_error",
"translation": "Invalid channel id"
@@ -6514,6 +6594,10 @@
"id": "store.sql_session.update_last_activity.app_error",
"translation": "Unable to update the last_activity_at"
},
{
"id": "store.sql_session.update_props.app_error",
"translation": "Unable to update session props"
},
{
"id": "store.sql_session.update_roles.app_error",
"translation": "Unable to update the roles"
@@ -6810,6 +6894,26 @@
"id": "store.sql_user.count.app_error",
"translation": "UserCountOptions don't make sense"
},
{
"id": "store.sql_user.demote_user_to_guest.channel_members_update.app_error",
"translation": "Failed to update the user channels memberships"
},
{
"id": "store.sql_user.demote_user_to_guest.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_user.demote_user_to_guest.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_user.demote_user_to_guest.team_members_update.app_error",
"translation": "Failed to update the user teams memberships"
},
{
"id": "store.sql_user.demote_user_to_guest.user_update.app_error",
"translation": "Failed to update the user"
},
{
"id": "store.sql_user.get.app_error",
"translation": "We encountered an error finding the account"
@@ -6890,6 +6994,26 @@
"id": "store.sql_user.permanent_delete.app_error",
"translation": "Unable to delete the existing account"
},
{
"id": "store.sql_user.promote_guest.channel_members_update.app_error",
"translation": "Failed to update the user channels memberships"
},
{
"id": "store.sql_user.promote_guest.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_user.promote_guest.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_user.promote_guest.team_members_update.app_error",
"translation": "Failed to update the user teams memberships"
},
{
"id": "store.sql_user.promote_guest.user_update.app_error",
"translation": "Failed to update the user"
},
{
"id": "store.sql_user.save.app_error",
"translation": "Unable to save the account."

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

@@ -11,6 +11,7 @@ import (
type ChannelStats struct {
ChannelId string `json:"channel_id"`
MemberCount int64 `json:"member_count"`
GuestCount int64 `json:"guest_count"`
}
func (o *ChannelStats) ToJson() string {

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

@@ -1078,6 +1078,26 @@ func (c *Client4) UpdateUserPassword(userId, currentPassword, newPassword string
return CheckStatusOK(r), BuildResponse(r)
}
// PromoteGuestToUser convert a guest into a regular user
func (c *Client4) PromoteGuestToUser(guestId string) (bool, *Response) {
r, err := c.DoApiPost(c.GetUserRoute(guestId)+"/promote", "")
if err != nil {
return false, BuildErrorResponse(r, err)
}
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
// DemoteUserToGuest convert a regular user into a guest
func (c *Client4) DemoteUserToGuest(guestId string) (bool, *Response) {
r, err := c.DoApiPost(c.GetUserRoute(guestId)+"/demote", "")
if err != nil {
return false, BuildErrorResponse(r, err)
}
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
// UpdateUserRoles updates a user's roles in the system. A user can have "system_user" and "system_admin" roles.
func (c *Client4) UpdateUserRoles(userId, roles string) (bool, *Response) {
requestBody := map[string]string{"roles": roles}
@@ -1904,6 +1924,21 @@ func (c *Client4) InviteUsersToTeam(teamId string, userEmails []string) (bool, *
return CheckStatusOK(r), BuildResponse(r)
}
// InviteGuestsToTeam invite guest by email to some channels in a team.
func (c *Client4) InviteGuestsToTeam(teamId string, userEmails []string, channels []string, message string) (bool, *Response) {
guestsInvite := GuestsInvite{
Emails: userEmails,
Channels: channels,
Message: message,
}
r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/invite-guests/email", guestsInvite.ToJson())
if err != nil {
return false, BuildErrorResponse(r, err)
}
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
// InvalidateEmailInvites will invalidate active email invitations that have not been accepted by the user.
func (c *Client4) InvalidateEmailInvites() (bool, *Response) {
r, err := c.DoApiDelete(c.GetTeamsRoute() + "/invites/email")

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

@@ -2312,6 +2312,31 @@ func (s *DisplaySettings) SetDefaults() {
}
}
type GuestAccountsSettings struct {
Enable *bool
AllowEmailAccounts *bool
EnforceMultifactorAuthentication *bool
RestrictCreationToDomains *string
}
func (s *GuestAccountsSettings) SetDefaults() {
if s.Enable == nil {
s.Enable = NewBool(false)
}
if s.AllowEmailAccounts == nil {
s.AllowEmailAccounts = NewBool(true)
}
if s.EnforceMultifactorAuthentication == nil {
s.EnforceMultifactorAuthentication = NewBool(false)
}
if s.RestrictCreationToDomains == nil {
s.RestrictCreationToDomains = NewString("")
}
}
type ImageProxySettings struct {
Enable *bool
ImageProxyType *string
@@ -2388,6 +2413,7 @@ type Config struct {
JobSettings JobSettings
PluginSettings PluginSettings
DisplaySettings DisplaySettings
GuestAccountsSettings GuestAccountsSettings
ImageProxySettings ImageProxySettings
}
@@ -2471,6 +2497,7 @@ func (o *Config) SetDefaults() {
o.JobSettings.SetDefaults()
o.MessageExportSettings.SetDefaults()
o.DisplaySettings.SetDefaults()
o.GuestAccountsSettings.SetDefaults()
o.ImageProxySettings.SetDefaults(o.ServiceSettings)
}

53
model/guest_invite.go Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
type GuestsInvite struct {
Emails []string `json:"emails"`
Channels []string `json:"channels"`
Message string `json:"message"`
}
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (i *GuestsInvite) IsValid() *AppError {
if len(i.Emails) == 0 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.emails.app_error", nil, "", http.StatusBadRequest)
}
for _, email := range i.Emails {
if len(email) > USER_EMAIL_MAX_LENGTH || len(email) == 0 || !IsValidEmail(email) {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.email.app_error", nil, "email="+email, http.StatusBadRequest)
}
}
if len(i.Channels) == 0 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channels.app_error", nil, "", http.StatusBadRequest)
}
for _, channel := range i.Channels {
if len(channel) != 26 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channel.app_error", nil, "channel="+channel, http.StatusBadRequest)
}
}
return nil
}
// GuestsInviteFromJson will decode the input and return a GuestsInvite
func GuestsInviteFromJson(data io.Reader) *GuestsInvite {
var invite *GuestsInvite
json.NewDecoder(data).Decode(&invite)
return invite
}
func (invite *GuestsInvite) ToJson() string {
b, _ := json.Marshal(invite)
return string(b)
}

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

@@ -59,6 +59,7 @@ type Features struct {
MessageExport *bool `json:"message_export"`
CustomPermissionsSchemes *bool `json:"custom_permissions_schemes"`
CustomTermsOfService *bool `json:"custom_terms_of_service"`
GuestAccountsPermissions *bool `json:"guest_accounts_permissions"`
// after we enabled more features we'll need to control them with this
FutureFeatures *bool `json:"future_features"`
@@ -81,6 +82,7 @@ func (f *Features) ToMap() map[string]interface{} {
"data_retention": *f.DataRetention,
"message_export": *f.MessageExport,
"custom_permissions_schemes": *f.CustomPermissionsSchemes,
"guest_accounts_permissions": *f.GuestAccountsPermissions,
"future": *f.FutureFeatures,
}
}
@@ -162,6 +164,10 @@ func (f *Features) SetDefaults() {
f.CustomPermissionsSchemes = NewBool(*f.FutureFeatures)
}
if f.GuestAccountsPermissions == nil {
f.GuestAccountsPermissions = NewBool(*f.FutureFeatures)
}
if f.CustomTermsOfService == nil {
f.CustomTermsOfService = NewBool(*f.FutureFeatures)
}

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

@@ -52,6 +52,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckTrue(t, *f.DataRetention)
CheckTrue(t, *f.MessageExport)
CheckTrue(t, *f.CustomPermissionsSchemes)
CheckTrue(t, *f.GuestAccountsPermissions)
CheckTrue(t, *f.FutureFeatures)
f = Features{}
@@ -73,6 +74,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
*f.DataRetention = true
*f.MessageExport = true
*f.CustomPermissionsSchemes = true
*f.GuestAccountsPermissions = true
*f.EmailNotificationContents = true
f.SetDefaults()
@@ -93,6 +95,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckTrue(t, *f.DataRetention)
CheckTrue(t, *f.MessageExport)
CheckTrue(t, *f.CustomPermissionsSchemes)
CheckTrue(t, *f.GuestAccountsPermissions)
CheckFalse(t, *f.FutureFeatures)
}
@@ -176,6 +179,7 @@ func TestLicenseToFromJson(t *testing.T) {
CheckBool(t, *f1.DataRetention, *f.DataRetention)
CheckBool(t, *f1.MessageExport, *f.MessageExport)
CheckBool(t, *f1.CustomPermissionsSchemes, *f.CustomPermissionsSchemes)
CheckBool(t, *f1.GuestAccountsPermissions, *f.GuestAccountsPermissions)
CheckBool(t, *f1.FutureFeatures, *f.FutureFeatures)
invalid := `{"asdf`

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

@@ -87,6 +87,9 @@ var PERMISSION_READ_OTHERS_BOTS *Permission
var PERMISSION_MANAGE_BOTS *Permission
var PERMISSION_MANAGE_OTHERS_BOTS *Permission
var PERMISSION_VIEW_MEMBERS *Permission
var PERMISSION_INVITE_GUEST *Permission
var PERMISSION_PROMOTE_GUEST *Permission
var PERMISSION_DEMOTE_TO_GUEST *Permission
// General permission that encompasses all system admin functions
// in the future this could be broken up to allow access to some
@@ -533,6 +536,25 @@ func initializePermissions() {
"authentication.permisssions.view_members.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_INVITE_GUEST = &Permission{
"invite_guest",
"authentication.permissions.invite_guest.name",
"authentication.permissions.invite_guest.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_PROMOTE_GUEST = &Permission{
"promote_guest",
"authentication.permissions.promote_guest.name",
"authentication.permissions.promote_guest.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_DEMOTE_TO_GUEST = &Permission{
"demote_to_guest",
"authentication.permissions.demote_to_guest.name",
"authentication.permissions.demote_to_guest.description",
PERMISSION_SCOPE_SYSTEM,
}
ALL_PERMISSIONS = []*Permission{
PERMISSION_INVITE_USER,
@@ -606,6 +628,9 @@ func initializePermissions() {
PERMISSION_MANAGE_OTHERS_BOTS,
PERMISSION_MANAGE_SYSTEM,
PERMISSION_VIEW_MEMBERS,
PERMISSION_INVITE_GUEST,
PERMISSION_PROMOTE_GUEST,
PERMISSION_DEMOTE_TO_GUEST,
}
}

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

@@ -382,6 +382,9 @@ func MakeDefaultRoles() map[string]*Role {
PERMISSION_EDIT_OTHERS_POSTS.Id,
PERMISSION_MANAGE_OAUTH.Id,
PERMISSION_INVITE_USER.Id,
PERMISSION_INVITE_GUEST.Id,
PERMISSION_PROMOTE_GUEST.Id,
PERMISSION_DEMOTE_TO_GUEST.Id,
PERMISSION_DELETE_POST.Id,
PERMISSION_DELETE_OTHERS_POSTS.Id,
PERMISSION_CREATE_TEAM.Id,

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

@@ -22,6 +22,7 @@ const (
SESSION_PROP_IS_BOT = "is_bot"
SESSION_PROP_IS_BOT_VALUE = "true"
SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
SESSION_PROP_IS_GUEST = "is_guest"
SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
)

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

@@ -32,6 +32,9 @@ const (
CHANNEL_MEMBERS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
CHANNEL_MEMBERS_COUNTS_CACHE_SEC = 1800 // 30 mins
CHANNEL_GUESTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
CHANNEL_GUESTS_COUNTS_CACHE_SEC = 1800 // 30 mins
CHANNEL_CACHE_SEC = 900 // 15 mins
)
@@ -278,6 +281,7 @@ type publicChannel struct {
}
var channelMemberCountsCache = utils.NewLru(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE)
var channelGuestCountsCache = utils.NewLru(CHANNEL_GUESTS_COUNTS_CACHE_SIZE)
var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
var allChannelMembersNotifyPropsForChannelCache = utils.NewLru(ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE)
var channelCache = utils.NewLru(model.CHANNEL_CACHE_SIZE)
@@ -285,6 +289,7 @@ var channelByNameCache = utils.NewLru(model.CHANNEL_CACHE_SIZE)
func (s SqlChannelStore) ClearCaches() {
channelMemberCountsCache.Purge()
channelGuestCountsCache.Purge()
allChannelMembersForUserCache.Purge()
allChannelMembersNotifyPropsForChannelCache.Purge()
channelCache.Purge()
@@ -1654,6 +1659,69 @@ func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (
return count, nil
}
func (s SqlChannelStore) InvalidateGuestCount(channelId string) {
channelGuestCountsCache.Remove(channelId)
if s.metrics != nil {
s.metrics.IncrementMemCacheInvalidationCounter("Channel Guest Counts - Remove by ChannelId")
}
}
func (s SqlChannelStore) GetGuestCountFromCache(channelId string) int64 {
if cacheItem, ok := channelGuestCountsCache.Get(channelId); ok {
if s.metrics != nil {
s.metrics.IncrementMemCacheHitCounter("Channel Guest Counts")
}
return cacheItem.(int64)
}
if s.metrics != nil {
s.metrics.IncrementMemCacheMissCounter("Channel Guest Counts")
}
count, err := s.GetGuestCount(channelId, true)
if err != nil {
return 0
}
return count
}
func (s SqlChannelStore) GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
if allowFromCache {
if cacheItem, ok := channelGuestCountsCache.Get(channelId); ok {
if s.metrics != nil {
s.metrics.IncrementMemCacheHitCounter("Channel Guest Counts")
}
return cacheItem.(int64), nil
}
}
if s.metrics != nil {
s.metrics.IncrementMemCacheMissCounter("Channel Guest Counts")
}
count, err := s.GetReplica().SelectInt(`
SELECT
count(*)
FROM
ChannelMembers,
Users
WHERE
ChannelMembers.UserId = Users.Id
AND ChannelMembers.ChannelId = :ChannelId
AND ChannelMembers.SchemeGuest = TRUE
AND Users.DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
if err != nil {
return 0, model.NewAppError("SqlChannelStore.GetGuestCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
}
if allowFromCache {
channelGuestCountsCache.AddWithExpiresInSecs(channelId, count, CHANNEL_GUESTS_COUNTS_CACHE_SEC)
}
return count, nil
}
func (s SqlChannelStore) RemoveMember(channelId string, userId string) *model.AppError {
_, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE ChannelId = :ChannelId AND UserId = :UserId", map[string]interface{}{"ChannelId": channelId, "UserId": userId})
if err != nil {
@@ -1871,8 +1939,7 @@ func (s SqlChannelStore) AnalyticsDeletedTypeCount(teamId string, channelType st
func (s SqlChannelStore) GetMembersForUser(teamId string, userId string) (*model.ChannelMembers, *model.AppError) {
var dbMembers channelMemberWithSchemeRolesList
_, err := s.GetReplica().Select(&dbMembers, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelMembers.UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
_, err := s.GetReplica().Select(&dbMembers, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelMembers.UserId = :UserId AND Teams.Id = :TeamId", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
return nil, model.NewAppError("SqlChannelStore.GetMembersForUser", "store.sql_channel.get_members.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
}

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

@@ -206,6 +206,23 @@ func (me SqlSessionStore) UpdateDeviceId(id string, deviceId string, expiresAt i
return deviceId, nil
}
func (me SqlSessionStore) UpdateProps(session *model.Session) *model.AppError {
oldSession, appErr := me.Get(session.Id)
if appErr != nil {
return appErr
}
oldSession.Props = session.Props
count, err := me.GetMaster().Update(oldSession)
if err != nil {
return model.NewAppError("SqlSessionStore.UpdateProps", "store.sql_session.update_props.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if count != 1 {
return model.NewAppError("SqlSessionStore.UpdateProps", "store.sql_session.update_props.app_error", nil, "", http.StatusInternalServerError)
}
return nil
}
func (me SqlSessionStore) AnalyticsSessionCount() (int64, *model.AppError) {
query :=
`SELECT

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

@@ -23,7 +23,7 @@ func NewSqlTokenStore(sqlStore SqlStore) store.TokenStore {
table := db.AddTableWithName(model.Token{}, "Tokens").SetKeys(false, "Token")
table.ColMap("Token").SetMaxSize(64)
table.ColMap("Type").SetMaxSize(64)
table.ColMap("Extra").SetMaxSize(128)
table.ColMap("Extra").SetMaxSize(2048)
}
return s

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

@@ -710,6 +710,12 @@ func UpgradeDatabaseToVersion514(sqlStore SqlStore) {
// TODO: Uncomment following condition when version 5.14.0 is released
// if shouldPerformUpgrade(sqlStore, VERSION_5_13_0, VERSION_5_14_0) {
if sqlStore.DriverName() == model.DATABASE_DRIVER_POSTGRES {
sqlStore.GetMaster().Exec("ALTER TABLE Tokens ALTER COLUMN Extra TYPE varchar(2048)")
} else if sqlStore.DriverName() == model.DATABASE_DRIVER_MYSQL {
sqlStore.GetMaster().Exec("ALTER TABLE Tokens MODIFY Extra text")
}
// saveSchemaVersion(sqlStore, VERSION_5_14_0)
// }
}

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

@@ -1066,7 +1066,7 @@ func (us SqlUserStore) PermanentDelete(userId string) *model.AppError {
}
func (us SqlUserStore) Count(options model.UserCountOptions) (int64, *model.AppError) {
query := sq.Select("COUNT(DISTINCT u.Id)").From("Users AS u")
query := us.getQueryBuilder().Select("COUNT(DISTINCT u.Id)").From("Users AS u")
if !options.IncludeDeleted {
query = query.Where("u.DeleteAt = 0")
@@ -1644,3 +1644,140 @@ func applyViewRestrictionsFilter(query sq.SelectBuilder, restrictions *model.Vie
return resultQuery
}
func (us SqlUserStore) PromoteGuestToUser(userId string) *model.AppError {
transaction, err := us.GetMaster().Begin()
if err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.promote_guest.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
}
defer finalizeTransaction(transaction)
user, appErr := us.Get(userId)
if appErr != nil {
return appErr
}
roles := user.GetRoles()
for idx, role := range roles {
if role == "system_guest" {
roles[idx] = "system_user"
}
}
query := us.getQueryBuilder().Update("Users").
Set("Roles", strings.Join(roles, " ")).
Where(sq.Eq{"Id": userId})
queryString, args, err := query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err = transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.promote_guest.user_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
query = us.getQueryBuilder().Update("ChannelMembers").
Set("SchemeUser", true).
Set("SchemeGuest", false).
Where(sq.Eq{"UserId": userId})
queryString, args, err = query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err = transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.promote_guest.channel_members_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
query = us.getQueryBuilder().Update("TeamMembers").
Set("SchemeUser", true).
Set("SchemeGuest", false).
Where(sq.Eq{"UserId": userId})
queryString, args, err = query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err := transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.promote_guest.team_members_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
if err := transaction.Commit(); err != nil {
return model.NewAppError("SqlUserStore.PromoteGuestToUser", "store.sql_user.promote_guest.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}
func (us SqlUserStore) DemoteUserToGuest(userId string) *model.AppError {
transaction, err := us.GetMaster().Begin()
if err != nil {
return model.NewAppError("SqlUserStore.DemoteUserToGuest", "store.sql_user.demote_user_to_guest.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
}
defer finalizeTransaction(transaction)
user, appErr := us.Get(userId)
if appErr != nil {
return appErr
}
roles := user.GetRoles()
newRoles := []string{}
for _, role := range roles {
if role == "system_user" {
newRoles = append(newRoles, "system_guest")
} else if role != "system_admin" {
newRoles = append(newRoles, role)
}
}
query := us.getQueryBuilder().Update("Users").
Set("Roles", strings.Join(newRoles, " ")).
Where(sq.Eq{"Id": userId})
queryString, args, err := query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err = transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.demote_user_to_guest.user_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
query = us.getQueryBuilder().Update("ChannelMembers").
Set("SchemeUser", false).
Set("SchemeGuest", true).
Where(sq.Eq{"UserId": userId})
queryString, args, err = query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err = transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.demote_user_to_guest.channel_members_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
query = us.getQueryBuilder().Update("TeamMembers").
Set("SchemeUser", false).
Set("SchemeGuest", true).
Where(sq.Eq{"UserId": userId})
queryString, args, err = query.ToSql()
if err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, err := transaction.Exec(queryString, args...); err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.demote_user_to_guest.team_members_update.app_error", nil, "user_id="+userId, http.StatusInternalServerError)
}
if err := transaction.Commit(); err != nil {
return model.NewAppError("SqlUserStore.DemoteGuestToUser", "store.sql_user.demote_user_to_guest.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}

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

@@ -172,6 +172,9 @@ type ChannelStore interface {
InvalidateMemberCount(channelId string)
GetMemberCountFromCache(channelId string) int64
GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError)
InvalidateGuestCount(channelId string)
GetGuestCountFromCache(channelId string) int64
GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError)
GetPinnedPosts(channelId string) (*model.PostList, *model.AppError)
RemoveMember(channelId string, userId string) *model.AppError
PermanentDeleteMembersByUser(userId string) *model.AppError
@@ -307,6 +310,8 @@ type UserStore interface {
Count(options model.UserCountOptions) (int64, *model.AppError)
GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError)
GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError)
PromoteGuestToUser(userID string) *model.AppError
DemoteUserToGuest(userID string) *model.AppError
}
type BotStore interface {
@@ -328,6 +333,7 @@ type SessionStore interface {
UpdateLastActivityAt(sessionId string, time int64) *model.AppError
UpdateRoles(userId string, roles string) (string, *model.AppError)
UpdateDeviceId(id string, deviceId string, expiresAt int64) (string, *model.AppError)
UpdateProps(session *model.Session) *model.AppError
AnalyticsSessionCount() (int64, *model.AppError)
Cleanup(expiryTime int64, batchSize int64)
}

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

@@ -65,6 +65,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetMember", func(t *testing.T) { testGetMember(t, ss) })
t.Run("GetMemberForPost", func(t *testing.T) { testChannelStoreGetMemberForPost(t, ss) })
t.Run("GetMemberCount", func(t *testing.T) { testGetMemberCount(t, ss) })
t.Run("GetGuestCount", func(t *testing.T) { testGetGuestCount(t, ss) })
t.Run("SearchMore", func(t *testing.T) { testChannelStoreSearchMore(t, ss) })
t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) })
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
@@ -2045,6 +2046,124 @@ func testGetMemberCount(t *testing.T, ss store.Store) {
}
}
func testGetGuestCount(t *testing.T, ss store.Store) {
teamId := model.NewId()
c1 := model.Channel{
TeamId: teamId,
DisplayName: "Channel1",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err := ss.Channel().Save(&c1, -1)
require.Nil(t, err)
c2 := model.Channel{
TeamId: teamId,
DisplayName: "Channel2",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err = ss.Channel().Save(&c2, -1)
require.Nil(t, err)
t.Run("Regular member doesn't count", func(t *testing.T) {
u1 := &model.User{
Email: MakeEmail(),
DeleteAt: 0,
Roles: model.SYSTEM_USER_ROLE_ID,
}
_, err = ss.User().Save(u1)
require.Nil(t, err)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
m1 := model.ChannelMember{
ChannelId: c1.Id,
UserId: u1.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
}
_, err = ss.Channel().SaveMember(&m1)
require.Nil(t, err)
count, channelErr := ss.Channel().GetGuestCount(c1.Id, false)
require.Nil(t, channelErr)
require.Equal(t, count, int64(0))
})
t.Run("Guest member does count", func(t *testing.T) {
u2 := model.User{
Email: MakeEmail(),
DeleteAt: 0,
Roles: model.SYSTEM_GUEST_ROLE_ID,
}
_, err = ss.User().Save(&u2)
require.Nil(t, err)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
m2 := model.ChannelMember{
ChannelId: c1.Id,
UserId: u2.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: true,
}
_, err = ss.Channel().SaveMember(&m2)
require.Nil(t, err)
count, channelErr := ss.Channel().GetGuestCount(c1.Id, false)
require.Nil(t, channelErr)
require.Equal(t, count, int64(1))
})
t.Run("make sure members of other channels aren't counted", func(t *testing.T) {
u3 := model.User{
Email: MakeEmail(),
DeleteAt: 0,
Roles: model.SYSTEM_GUEST_ROLE_ID,
}
_, err = ss.User().Save(&u3)
require.Nil(t, err)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
m3 := model.ChannelMember{
ChannelId: c2.Id,
UserId: u3.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: true,
}
_, err = ss.Channel().SaveMember(&m3)
require.Nil(t, err)
count, channelErr := ss.Channel().GetGuestCount(c1.Id, false)
require.Nil(t, channelErr)
require.Equal(t, count, int64(1))
})
t.Run("make sure inactive users aren't counted", func(t *testing.T) {
u4 := &model.User{
Email: MakeEmail(),
DeleteAt: 10000,
Roles: model.SYSTEM_GUEST_ROLE_ID,
}
_, err = ss.User().Save(u4)
require.Nil(t, err)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u4.Id}, -1))
m4 := model.ChannelMember{
ChannelId: c1.Id,
UserId: u4.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: true,
}
_, err = ss.Channel().SaveMember(&m4)
require.Nil(t, err)
count, channelErr := ss.Channel().GetGuestCount(c1.Id, false)
require.Nil(t, channelErr)
require.Equal(t, count, int64(1))
})
}
func testChannelStoreSearchMore(t *testing.T, ss store.Store) {
teamId := model.NewId()
otherTeamId := model.NewId()

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

@@ -744,6 +744,43 @@ func (_m *ChannelStore) GetFromMaster(id string) (*model.Channel, *model.AppErro
return r0, r1
}
// GetGuestCount provides a mock function with given fields: channelId, allowFromCache
func (_m *ChannelStore) GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
ret := _m.Called(channelId, allowFromCache)
var r0 int64
if rf, ok := ret.Get(0).(func(string, bool) int64); ok {
r0 = rf(channelId, allowFromCache)
} else {
r0 = ret.Get(0).(int64)
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, bool) *model.AppError); ok {
r1 = rf(channelId, allowFromCache)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetGuestCountFromCache provides a mock function with given fields: channelId
func (_m *ChannelStore) GetGuestCountFromCache(channelId string) int64 {
ret := _m.Called(channelId)
var r0 int64
if rf, ok := ret.Get(0).(func(string) int64); ok {
r0 = rf(channelId)
} else {
r0 = ret.Get(0).(int64)
}
return r0
}
// GetMember provides a mock function with given fields: channelId, userId
func (_m *ChannelStore) GetMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
ret := _m.Called(channelId, userId)
@@ -1092,6 +1129,11 @@ func (_m *ChannelStore) InvalidateChannelByName(teamId string, name string) {
_m.Called(teamId, name)
}
// InvalidateGuestCount provides a mock function with given fields: channelId
func (_m *ChannelStore) InvalidateGuestCount(channelId string) {
_m.Called(channelId)
}
// InvalidateMemberCount provides a mock function with given fields: channelId
func (_m *ChannelStore) InvalidateMemberCount(channelId string) {
_m.Called(channelId)

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

@@ -227,6 +227,22 @@ func (_m *SessionStore) UpdateLastActivityAt(sessionId string, time int64) *mode
return r0
}
// UpdateProps provides a mock function with given fields: session
func (_m *SessionStore) UpdateProps(session *model.Session) *model.AppError {
ret := _m.Called(session)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(*model.Session) *model.AppError); ok {
r0 = rf(session)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// UpdateRoles provides a mock function with given fields: userId, roles
func (_m *SessionStore) UpdateRoles(userId string, roles string) (string, *model.AppError) {
ret := _m.Called(userId, roles)

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

@@ -126,6 +126,22 @@ func (_m *UserStore) Count(options model.UserCountOptions) (int64, *model.AppErr
return r0, r1
}
// DemoteUserToGuest provides a mock function with given fields: userID
func (_m *UserStore) DemoteUserToGuest(userID string) *model.AppError {
ret := _m.Called(userID)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
r0 = rf(userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// Get provides a mock function with given fields: id
func (_m *UserStore) Get(id string) (*model.User, *model.AppError) {
ret := _m.Called(id)
@@ -905,6 +921,22 @@ func (_m *UserStore) PermanentDelete(userId string) *model.AppError {
return r0
}
// PromoteGuestToUser provides a mock function with given fields: userID
func (_m *UserStore) PromoteGuestToUser(userID string) *model.AppError {
ret := _m.Called(userID)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
r0 = rf(userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// ResetLastPictureUpdate provides a mock function with given fields: userId
func (_m *UserStore) ResetLastPictureUpdate(userId string) *model.AppError {
ret := _m.Called(userId)

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

@@ -78,6 +78,8 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetUsersBatchForIndexing", func(t *testing.T) { testUserStoreGetUsersBatchForIndexing(t, ss) })
t.Run("GetTeamGroupUsers", func(t *testing.T) { testUserStoreGetTeamGroupUsers(t, ss) })
t.Run("GetChannelGroupUsers", func(t *testing.T) { testUserStoreGetChannelGroupUsers(t, ss) })
t.Run("PromoteGuestToUser", func(t *testing.T) { testUserStorePromoteGuestToUser(t, ss) })
t.Run("DemoteUserToGuest", func(t *testing.T) { testUserStoreDemoteUserToGuest(t, ss) })
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
}
@@ -4154,6 +4156,604 @@ func testUserStoreGetChannelGroupUsers(t *testing.T, ss store.Store) {
requireNUsers(2)
}
func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
// create users
t.Run("Must do nothing with regular user", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.False(t, updatedChannelMember.SchemeGuest)
require.True(t, updatedChannelMember.SchemeUser)
})
t.Run("Must do nothing with admin user", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user system_admin",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user system_admin", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.False(t, updatedChannelMember.SchemeGuest)
require.True(t, updatedChannelMember.SchemeUser)
})
t.Run("Must work with guest user without teams or channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", updatedUser.Roles)
})
t.Run("Must work with guest user with teams but no channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
})
t.Run("Must work with guest user with teams and channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.False(t, updatedChannelMember.SchemeGuest)
require.True(t, updatedChannelMember.SchemeUser)
})
t.Run("Must work with guest user with teams and channels and custom role", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest custom_role",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_user custom_role", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.False(t, updatedChannelMember.SchemeGuest)
require.True(t, updatedChannelMember.SchemeUser)
})
t.Run("Must no change any other user guest role", func(t *testing.T) {
id := model.NewId()
user1, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
teamId1 := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId1,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user1.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
id = model.NewId()
user2, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
teamId2 := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: true, SchemeUser: false}, 999))
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user2.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().PromoteGuestToUser(user1.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user1.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId1, user1.Id)
require.Nil(t, err)
require.False(t, updatedTeamMember.SchemeGuest)
require.True(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user1.Id)
require.Nil(t, err)
require.False(t, updatedChannelMember.SchemeGuest)
require.True(t, updatedChannelMember.SchemeUser)
notUpdatedUser, err := ss.User().Get(user2.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", notUpdatedUser.Roles)
notUpdatedTeamMember, err := ss.Team().GetMember(teamId2, user2.Id)
require.Nil(t, err)
require.True(t, notUpdatedTeamMember.SchemeGuest)
require.False(t, notUpdatedTeamMember.SchemeUser)
notUpdatedChannelMember, err := ss.Channel().GetMember(channel.Id, user2.Id)
require.Nil(t, err)
require.True(t, notUpdatedChannelMember.SchemeGuest)
require.False(t, notUpdatedChannelMember.SchemeUser)
})
}
func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
// create users
t.Run("Must do nothing with guest", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.True(t, updatedChannelMember.SchemeGuest)
require.False(t, updatedChannelMember.SchemeUser)
})
t.Run("Must demote properly an admin user", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user system_admin",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: true, SchemeUser: false, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
require.Nil(t, err)
require.True(t, updatedChannelMember.SchemeGuest)
require.False(t, updatedChannelMember.SchemeUser)
})
t.Run("Must work with user without teams or channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
})
t.Run("Must work with user with teams but no channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999))
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
})
t.Run("Must work with user with teams and channels", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
assert.Nil(t, err)
require.True(t, updatedChannelMember.SchemeGuest)
require.False(t, updatedChannelMember.SchemeUser)
})
t.Run("Must work with user with teams and channels and custom role", func(t *testing.T) {
id := model.NewId()
user, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user custom_role",
})
require.Nil(t, err)
teamId := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user.Id, SchemeGuest: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest custom_role", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId, user.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user.Id)
assert.Nil(t, err)
require.True(t, updatedChannelMember.SchemeGuest)
require.False(t, updatedChannelMember.SchemeUser)
})
t.Run("Must no change any other user role", func(t *testing.T) {
id := model.NewId()
user1, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
teamId1 := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: false, SchemeUser: true}, 999))
channel, err := ss.Channel().Save(&model.Channel{
TeamId: teamId1,
DisplayName: "Channel name",
Name: "channel-" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user1.Id, SchemeGuest: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
id = model.NewId()
user2, err := ss.User().Save(&model.User{
Email: id + "@test.com",
Username: "un_" + id,
Nickname: "nn_" + id,
FirstName: "f_" + id,
LastName: "l_" + id,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
teamId2 := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: false, SchemeUser: true}, 999))
_, err = ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: user2.Id, SchemeGuest: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
err = ss.User().DemoteUserToGuest(user1.Id)
assert.Nil(t, err)
updatedUser, err := ss.User().Get(user1.Id)
assert.Nil(t, err)
require.Equal(t, "system_guest", updatedUser.Roles)
updatedTeamMember, err := ss.Team().GetMember(teamId1, user1.Id)
require.Nil(t, err)
require.True(t, updatedTeamMember.SchemeGuest)
require.False(t, updatedTeamMember.SchemeUser)
updatedChannelMember, err := ss.Channel().GetMember(channel.Id, user1.Id)
assert.Nil(t, err)
require.True(t, updatedChannelMember.SchemeGuest)
require.False(t, updatedChannelMember.SchemeUser)
notUpdatedUser, err := ss.User().Get(user2.Id)
assert.Nil(t, err)
require.Equal(t, "system_user", notUpdatedUser.Roles)
notUpdatedTeamMember, err := ss.Team().GetMember(teamId2, user2.Id)
require.Nil(t, err)
require.False(t, notUpdatedTeamMember.SchemeGuest)
require.True(t, notUpdatedTeamMember.SchemeUser)
notUpdatedChannelMember, err := ss.Channel().GetMember(channel.Id, user2.Id)
assert.Nil(t, err)
require.False(t, notUpdatedChannelMember.SchemeGuest)
require.True(t, notUpdatedChannelMember.SchemeUser)
})
}
func testUserStoreResetLastPictureUpdate(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()

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

@@ -22,6 +22,7 @@
<p style="margin: 30px 0 15px">
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
</p>
<p>{{.Props.Message}}</p>
<br/>
<p>{{.Html.ExtraInfo}}<br/><br/><a href='{{.Props.TeamURL}}'>{{.Props.TeamURL}}</a></p>
</td>

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

@@ -155,6 +155,7 @@ func GetClientLicense(l *model.License) map[string]string {
props["EmailNotificationContents"] = strconv.FormatBool(*l.Features.EmailNotificationContents)
props["MessageExport"] = strconv.FormatBool(*l.Features.MessageExport)
props["CustomPermissionsSchemes"] = strconv.FormatBool(*l.Features.CustomPermissionsSchemes)
props["GuestAccountsPermissions"] = strconv.FormatBool(*l.Features.GuestAccountsPermissions)
props["CustomTermsOfService"] = strconv.FormatBool(*l.Features.CustomTermsOfService)
}

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

@@ -115,6 +115,9 @@ func (c *Context) MfaRequired() {
c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized)
return
} else {
if user.IsGuest() && !*c.App.Config().GuestAccountsSettings.EnforceMultifactorAuthentication {
return
}
// Only required for email and ldap accounts
if user.AuthService != "" &&
user.AuthService != model.USER_AUTH_SERVICE_EMAIL &&