diff --git a/api/team.go b/api/team.go index 0f7298b57f..cb942bb35b 100644 --- a/api/team.go +++ b/api/team.go @@ -394,11 +394,23 @@ func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) { func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { invites := model.InvitesFromJson(r.Body) if len(invites.Invites) == 0 { - c.Err = model.NewLocAppError("Team.InviteMembers", "api.team.invite_members.no_one.app_error", nil, "") + c.Err = model.NewLocAppError("inviteMembers", "api.team.invite_members.no_one.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } + if utils.IsLicensed { + if *utils.Cfg.TeamSettings.RestrictTeamInvite == model.TEAM_INVITE_SYSTEM_ADMIN && !c.IsSystemAdmin() { + c.Err = model.NewLocAppError("inviteMembers", "api.team.invite_members.restricted_system_admin.app_error", nil, "") + return + } + + if *utils.Cfg.TeamSettings.RestrictTeamInvite == model.TEAM_INVITE_TEAM_ADMIN && !c.IsTeamAdmin() { + c.Err = model.NewLocAppError("inviteMembers", "api.team.invite_members.restricted_team_admin.app_error", nil, "") + return + } + } + tchan := Srv.Store.Team().Get(c.TeamId) uchan := Srv.Store.User().Get(c.Session.UserId) diff --git a/api/team_test.go b/api/team_test.go index 30952b4d8c..91c73bed54 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -363,9 +363,10 @@ func TestTeamPermDelete(t *testing.T) { } func TestInviteMembers(t *testing.T) { - th := Setup().InitBasic() + th := Setup().InitBasic().InitSystemAdmin() th.BasicClient.Logout() Client := th.BasicClient + SystemAdminClient := th.SystemAdminClient team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -389,10 +390,54 @@ func TestInviteMembers(t *testing.T) { t.Fatal(err) } - invites = &model.Invites{Invites: []map[string]string{}} - if _, err := Client.InviteMembers(invites); err == nil { + invites2 := &model.Invites{Invites: []map[string]string{}} + if _, err := Client.InviteMembers(invites2); err == nil { t.Fatal("Should have errored out on no invites to send") } + + restrictTeamInvite := *utils.Cfg.TeamSettings.RestrictTeamInvite + defer func() { + *utils.Cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite + }() + *utils.Cfg.TeamSettings.RestrictTeamInvite = model.TEAM_INVITE_TEAM_ADMIN + + th.LoginBasic2() + LinkUserToTeam(th.BasicUser2, team) + + if _, err := Client.InviteMembers(invites); err != nil { + t.Fatal(err) + } + + isLicensed := utils.IsLicensed + defer func() { + utils.IsLicensed = isLicensed + }() + utils.IsLicensed = true + + if _, err := Client.InviteMembers(invites); err == nil { + t.Fatal("should have errored not team admin and licensed") + } + + UpdateUserToTeamAdmin(th.BasicUser2, team) + Client.Logout() + th.LoginBasic2() + Client.SetTeamId(team.Id) + + if _, err := Client.InviteMembers(invites); err != nil { + t.Fatal(err) + } + + *utils.Cfg.TeamSettings.RestrictTeamInvite = model.TEAM_INVITE_SYSTEM_ADMIN + + if _, err := Client.InviteMembers(invites); err == nil { + t.Fatal("should have errored not system admin and licensed") + } + + LinkUserToTeam(th.SystemAdminUser, team) + + if _, err := SystemAdminClient.InviteMembers(invites); err != nil { + t.Fatal(err) + } } func TestUpdateTeamDisplayName(t *testing.T) { diff --git a/config/config.json b/config/config.json index 4b93d36f69..eeb75d0c14 100644 --- a/config/config.json +++ b/config/config.json @@ -37,7 +37,8 @@ "RestrictTeamNames": true, "EnableCustomBrand": false, "CustomBrandText": "", - "RestrictDirectMessage": "any" + "RestrictDirectMessage": "any", + "RestrictTeamInvite": "system_admin" }, "SqlSettings": { "DriverName": "mysql", diff --git a/i18n/en.json b/i18n/en.json index 6f74d1d3e4..cfc82f8569 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1299,6 +1299,14 @@ "id": "api.team.invite_members.no_one.app_error", "translation": "No one to invite." }, + { + "id": "api.team.invite_members.restricted_system_admin.app_error", + "translation": "Inviting new users to a team is restricted to System Administrators." + }, + { + "id": "api.team.invite_members.restricted_team_admin.app_error", + "translation": "Inviting new users to a team is restricted to Team and System Administrators." + }, { "id": "api.team.invite_members.send.error", "translation": "Failed to send invite email successfully err=%v" diff --git a/model/config.go b/model/config.go index 51a3b252ea..1d9e078b6d 100644 --- a/model/config.go +++ b/model/config.go @@ -32,6 +32,10 @@ const ( DIRECT_MESSAGE_ANY = "any" DIRECT_MESSAGE_TEAM = "team" + TEAM_INVITE_ALL = "all" + TEAM_INVITE_TEAM_ADMIN = "team_admin" + TEAM_INVITE_SYSTEM_ADMIN = "system_admin" + FAKE_SETTING = "********************************" RESTRICT_EMOJI_CREATION_ALL = "all" @@ -174,6 +178,7 @@ type TeamSettings struct { EnableCustomBrand *bool CustomBrandText *string RestrictDirectMessage *string + RestrictTeamInvite *string } type LdapSettings struct { @@ -346,6 +351,11 @@ func (o *Config) SetDefaults() { *o.TeamSettings.RestrictDirectMessage = DIRECT_MESSAGE_ANY } + if o.TeamSettings.RestrictTeamInvite == nil { + o.TeamSettings.RestrictTeamInvite = new(string) + *o.TeamSettings.RestrictTeamInvite = TEAM_INVITE_ALL + } + if o.EmailSettings.EnableSignInWithEmail == nil { o.EmailSettings.EnableSignInWithEmail = new(bool) diff --git a/utils/config.go b/utils/config.go index 60d09dee3d..58980ba7f9 100644 --- a/utils/config.go +++ b/utils/config.go @@ -210,6 +210,7 @@ func getClientConfig(c *model.Config) map[string]string { props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer) props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames) props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage + props["RestrictTeamInvite"] = *c.TeamSettings.RestrictTeamInvite props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index cb98c8ab1c..b045ec5f49 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -180,6 +180,7 @@ export default class AdminSidebar extends React.Component { let license = null; let audits = null; + let policy = null; if (window.mm_config.BuildEnterpriseReady === 'true') { if (window.mm_license.IsLicensed === 'true') { @@ -210,6 +211,18 @@ export default class AdminSidebar extends React.Component { /> ); } + + policy = ( + + } + /> + ); } license = ( @@ -328,6 +341,7 @@ export default class AdminSidebar extends React.Component { /> } /> + {policy} ); @@ -58,14 +58,7 @@ export default class LocalizationSettings extends AdminSettings { renderSettings() { return ( - - } - > + + + + ); + } + + renderSettings() { + return ( + + + } + value={this.state.restrictTeamInvite} + onChange={this.handleChange} + helpText={ + + } + /> + + ); + } +} diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx index 9d6d7fb22a..c3b646e527 100644 --- a/webapp/components/navbar_dropdown.jsx +++ b/webapp/components/navbar_dropdown.jsx @@ -119,6 +119,16 @@ export default class NavbarDropdown extends React.Component { ); } + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_SYSTEM_ADMIN && !isSystemAdmin) { + teamLink = null; + inviteLink = null; + } else if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_TEAM_ADMIN && !isAdmin) { + teamLink = null; + inviteLink = null; + } + } } if (isAdmin) { diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx index 622b803378..8cb8733d74 100644 --- a/webapp/components/sidebar_right_menu.jsx +++ b/webapp/components/sidebar_right_menu.jsx @@ -184,6 +184,16 @@ export default class SidebarRightMenu extends React.Component { ); } + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_SYSTEM_ADMIN && !isSystemAdmin) { + teamLink = null; + inviteLink = null; + } else if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_TEAM_ADMIN && !isAdmin) { + teamLink = null; + inviteLink = null; + } + } } if (isAdmin) { diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx index 3928b7f20a..b0d831d96d 100644 --- a/webapp/components/tutorial/tutorial_intro_screens.jsx +++ b/webapp/components/tutorial/tutorial_intro_screens.jsx @@ -106,32 +106,45 @@ export default class TutorialIntroScreens extends React.Component { createScreenThree() { const team = TeamStore.getCurrent(); let inviteModalLink; + let inviteText; - if (team.type === Constants.INVITE_TEAM) { - inviteModalLink = ( - + if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_ALL) { + if (team.type === Constants.INVITE_TEAM) { + inviteModalLink = ( + + + + ); + } else { + inviteModalLink = ( + + + + ); + } + + inviteText = ( +

+ {inviteModalLink} - - ); - } else { - inviteModalLink = ( - - - +

); } @@ -170,13 +183,7 @@ export default class TutorialIntroScreens extends React.Component { defaultMessage='You’re all set' /> -

- {inviteModalLink} - -

+ {inviteText} {supportInfo}
Selecting \"Team and System Admins\" hides the email invitation and team invite link in the Main Menu from users who are not Team or System Admins. Note: If \"Get Team Invite Link\" is used to share a link, it will need to be regenerated after the desired users joined the team.

Selecting \"System Admins\" hides the email invitation and team invite link in the Main Menu from users who are not System Admins. Note: If \"Get Team Invite Link\" is used to share a link, it will need to be regenerated after the desired users joined the team.", "admin.general.privacy": "Privacy", - "admin.general.title": "General Settings", "admin.general.usersAndTeams": "Users and Teams", "admin.gitab.clientSecretDescription": "Obtain this value via the instructions above for logging into GitLab.", "admin.gitlab.EnableHtmlDesc": "
  1. Log in to your GitLab account and go to Profile Settings -> Applications.
  2. Enter Redirect URIs \"/login/gitlab/complete\" (example: http://localhost:8065/login/gitlab/complete) and \"/signup/gitlab/complete\".
  3. Then use \"Secret\" and \"Id\" fields from GitLab to complete the options below.
  4. Complete the Endpoint URLs below.
", @@ -488,6 +493,7 @@ "admin.sidebar.logs": "Logs", "admin.sidebar.notifications": "Notifications", "admin.sidebar.other": "OTHER", + "admin.sidebar.policy": "Policy", "admin.sidebar.privacy": "Privacy", "admin.sidebar.publicLinks": "Public Links", "admin.sidebar.push": "Mobile Push", diff --git a/webapp/routes/route_admin_console.jsx b/webapp/routes/route_admin_console.jsx index cd1144ae62..b088b430b6 100644 --- a/webapp/routes/route_admin_console.jsx +++ b/webapp/routes/route_admin_console.jsx @@ -10,6 +10,7 @@ import ConfigurationSettings from 'components/admin_console/configuration_settin import LocalizationSettings from 'components/admin_console/localization_settings.jsx'; import UsersAndTeamsSettings from 'components/admin_console/users_and_teams_settings.jsx'; import PrivacySettings from 'components/admin_console/privacy_settings.jsx'; +import PolicySettings from 'components/admin_console/policy_settings.jsx'; import LogSettings from 'components/admin_console/log_settings.jsx'; import EmailAuthenticationSettings from 'components/admin_console/email_authentication_settings.jsx'; import GitLabSettings from 'components/admin_console/gitlab_settings.jsx'; @@ -62,6 +63,10 @@ export default ( path='privacy' component={PrivacySettings} /> + ); + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_SYSTEM_ADMIN && !isSystemAdmin) { + inviteModalLink = null; + } else if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_TEAM_ADMIN && !isAdmin) { + inviteModalLink = null; + } + } + return (