diff options
-rw-r--r-- | api/team.go | 14 | ||||
-rw-r--r-- | api/team_test.go | 51 | ||||
-rw-r--r-- | config/config.json | 3 | ||||
-rw-r--r-- | i18n/en.json | 8 | ||||
-rw-r--r-- | model/config.go | 10 | ||||
-rw-r--r-- | utils/config.go | 1 | ||||
-rw-r--r-- | webapp/components/admin_console/admin_sidebar.jsx | 14 | ||||
-rw-r--r-- | webapp/components/admin_console/localization_settings.jsx | 13 | ||||
-rw-r--r-- | webapp/components/admin_console/policy_settings.jsx | 73 | ||||
-rw-r--r-- | webapp/components/navbar_dropdown.jsx | 10 | ||||
-rw-r--r-- | webapp/components/sidebar_right_menu.jsx | 10 | ||||
-rw-r--r-- | webapp/components/tutorial/tutorial_intro_screens.jsx | 67 | ||||
-rw-r--r-- | webapp/i18n/en.json | 8 | ||||
-rw-r--r-- | webapp/routes/route_admin_console.jsx | 5 | ||||
-rw-r--r-- | webapp/utils/channel_intro_messages.jsx | 15 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 5 |
16 files changed, 259 insertions, 48 deletions
diff --git a/api/team.go b/api/team.go index 0f7298b57..cb942bb35 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 30952b4d8..91c73bed5 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 4b93d36f6..eeb75d0c1 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 6f74d1d3e..cfc82f856 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1300,6 +1300,14 @@ "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 51a3b252e..1d9e078b6 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 60d09dee3..58980ba7f 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 cb98c8ab1..b045ec5f4 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 = ( + <AdminSidebarSection + name='policy' + title={ + <FormattedMessage + id='admin.sidebar.policy' + defaultMessage='Policy' + /> + } + /> + ); } license = ( @@ -328,6 +341,7 @@ export default class AdminSidebar extends React.Component { /> } /> + {policy} <AdminSidebarSection name='privacy' title={ diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx index 67cf26fee..c837ac277 100644 --- a/webapp/components/admin_console/localization_settings.jsx +++ b/webapp/components/admin_console/localization_settings.jsx @@ -49,8 +49,8 @@ export default class LocalizationSettings extends AdminSettings { return ( <h3> <FormattedMessage - id='admin.general.title' - defaultMessage='General Settings' + id='admin.general.localization' + defaultMessage='Localization' /> </h3> ); @@ -58,14 +58,7 @@ export default class LocalizationSettings extends AdminSettings { renderSettings() { return ( - <SettingsGroup - header={ - <FormattedMessage - id='admin.general.localization' - defaultMessage='Localization' - /> - } - > + <SettingsGroup> <DropdownSetting id='defaultServerLocale' values={this.state.languages} diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx new file mode 100644 index 000000000..7fe8e9460 --- /dev/null +++ b/webapp/components/admin_console/policy_settings.jsx @@ -0,0 +1,73 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import AdminSettings from './admin_settings.jsx'; +import SettingsGroup from './settings_group.jsx'; +import DropdownSetting from './dropdown_setting.jsx'; + +import Constants from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +export default class PolicySettings extends AdminSettings { + constructor(props) { + super(props); + + this.getConfigFromState = this.getConfigFromState.bind(this); + + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + restrictTeamInvite: props.config.TeamSettings.RestrictTeamInvite + }); + } + + getConfigFromState(config) { + config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite; + + return config; + } + + renderTitle() { + return ( + <h3> + <FormattedMessage + id='admin.general.policy' + defaultMessage='Policy' + /> + </h3> + ); + } + + renderSettings() { + return ( + <SettingsGroup> + <DropdownSetting + id='restrictTeamInvite' + values={[ + {value: Constants.TEAM_INVITE_ALL, text: Utils.localizeMessage('admin.general.policy.teamInviteAll', 'All team members')}, + {value: Constants.TEAM_INVITE_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.teamInviteAdmin', 'Team and System Admins')}, + {value: Constants.TEAM_INVITE_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.teamInviteSystemAdmin', 'System Admins')} + ]} + label={ + <FormattedMessage + id='admin.general.policy.teamInviteTitle' + defaultMessage='Enable sending team invites from:' + /> + } + value={this.state.restrictTeamInvite} + onChange={this.handleChange} + helpText={ + <FormattedHTMLMessage + id='admin.general.policy.teamInviteDescription' + defaultMessage='Selecting "All team members" allows any team member to invite others using an email invitation or team invite link.<br/><br/>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.<br/><br/>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.' + /> + } + /> + </SettingsGroup> + ); + } +} diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx index 9d6d7fb22..c3b646e52 100644 --- a/webapp/components/navbar_dropdown.jsx +++ b/webapp/components/navbar_dropdown.jsx @@ -119,6 +119,16 @@ export default class NavbarDropdown extends React.Component { </li> ); } + + 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 622b80337..8cb8733d7 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 { </li> ); } + + 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 3928b7f20..b0d831d96 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 = ( - <a - className='intro-links' - href='#' - onClick={GlobalActions.showInviteMemberModal} - > - <FormattedMessage - id='tutorial_intro.invite' - defaultMessage='Invite teammates' - /> - </a> - ); - } else { - inviteModalLink = ( - <a - className='intro-links' - href='#' - onClick={GlobalActions.showGetTeamInviteLinkModal} - > + if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_ALL) { + if (team.type === Constants.INVITE_TEAM) { + inviteModalLink = ( + <a + className='intro-links' + href='#' + onClick={GlobalActions.showInviteMemberModal} + > + <FormattedMessage + id='tutorial_intro.invite' + defaultMessage='Invite teammates' + /> + </a> + ); + } else { + inviteModalLink = ( + <a + className='intro-links' + href='#' + onClick={GlobalActions.showGetTeamInviteLinkModal} + > + <FormattedMessage + id='tutorial_intro.teamInvite' + defaultMessage='Invite teammates' + /> + </a> + ); + } + + inviteText = ( + <p> + {inviteModalLink} <FormattedMessage - id='tutorial_intro.teamInvite' - defaultMessage='Invite teammates' + id='tutorial_intro.whenReady' + defaultMessage=' when you’re ready.' /> - </a> + </p> ); } @@ -170,13 +183,7 @@ export default class TutorialIntroScreens extends React.Component { defaultMessage='You’re all set' /> </h3> - <p> - {inviteModalLink} - <FormattedMessage - id='tutorial_intro.whenReady' - defaultMessage=' when you’re ready.' - /> - </p> + {inviteText} {supportInfo} <FormattedMessage id='tutorial_intro.end' diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 47c1732bd..a632943cd 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -213,8 +213,13 @@ "admin.general.localization.serverLocaleDescription": "Default language for system messages and logs. Changing this will require a server restart before taking effect.", "admin.general.localization.serverLocaleTitle": "Default Server Language:", "admin.general.log": "Logging", + "admin.general.policy": "Policy", + "admin.general.policy.teamInviteAll": "All team members", + "admin.general.policy.teamInviteAdmin": "Team and System Admins", + "admin.general.policy.teamInviteSystemAdmin": "System Admins", + "admin.general.policy.teamInviteTitle": "Enable sending team invites from:", + "admin.general.policy.teamInviteDescription": "Selecting \"All team members\" allows any team member to invite others using an email invitation or team invite link.<br/><br/>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.<br/><br/>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": "<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs \"<your-mattermost-url>/login/gitlab/complete\" (example: http://localhost:8065/login/gitlab/complete) and \"<your-mattermost-url>/signup/gitlab/complete\". </li><li>Then use \"Secret\" and \"Id\" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>", @@ -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 cd1144ae6..b088b430b 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'; @@ -63,6 +64,10 @@ export default ( component={PrivacySettings} /> <Route + path='policy' + component={PolicySettings} + /> + <Route path='compliance' component={ComplianceSettings} /> diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx index c9dd30712..043894b7b 100644 --- a/webapp/utils/channel_intro_messages.jsx +++ b/webapp/utils/channel_intro_messages.jsx @@ -7,6 +7,8 @@ import EditChannelHeaderModal from 'components/edit_channel_header_modal.jsx'; import ToggleModalButton from 'components/toggle_modal_button.jsx'; import UserProfile from 'components/user_profile.jsx'; import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import Client from 'utils/web_client.jsx'; @@ -94,7 +96,7 @@ export function createOffTopicIntroMessage(channel) { } export function createDefaultIntroMessage(channel) { - const inviteModalLink = ( + let inviteModalLink = ( <a className='intro-links' href='#' @@ -108,6 +110,17 @@ export function createDefaultIntroMessage(channel) { </a> ); + 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 ( <div className='channel-intro'> <FormattedHTMLMessage diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 428a806e9..f6e929270 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -755,5 +755,8 @@ export default { MAX_PREV_MSGS: 100, POST_COLLAPSE_TIMEOUT: 1000 * 60 * 5, // five minutes LICENSE_EXPIRY_NOTIFICATION: 1000 * 60 * 60 * 24 * 15, // 15 days - LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15 // 15 days + LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15, // 15 days + TEAM_INVITE_ALL: 'all', + TEAM_INVITE_TEAM_ADMIN: 'team_admin', + TEAM_INVITE_SYSTEM_ADMIN: 'system_admin' }; |