* 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)
}