Guest accounts feature (#11428)
* 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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
fdde7c8287
Коммит
fe8a0f6485
@@ -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
Обычный файл
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
|
||||
)
|
||||
|
||||
Ссылка в новой задаче
Block a user