From 5f7cb8cfbf879aa0b0d43a7b7068688368fda9fc Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 6 Jul 2016 08:23:24 -0400 Subject: PLT-3346/PLT-3342/PLT-3360 EE: Add the ability to restrict channel management permissions (#3453) * EE: Add the ability to restrict channel management permissions * Always allow last user in a channel to delete that channel --- .../components/admin_console/policy_settings.jsx | 56 +++++++- webapp/components/channel_header.jsx | 157 +++++++++++++-------- webapp/components/more_channels.jsx | 56 +++++--- webapp/components/navbar_dropdown.jsx | 4 +- webapp/components/new_channel_modal.jsx | 65 ++++++--- webapp/components/sidebar.jsx | 77 ++++++---- webapp/components/sidebar_right_menu.jsx | 4 +- .../components/tutorial/tutorial_intro_screens.jsx | 2 +- 8 files changed, 294 insertions(+), 127 deletions(-) (limited to 'webapp/components') diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx index 7fe8e9460..c7031af7b 100644 --- a/webapp/components/admin_console/policy_settings.jsx +++ b/webapp/components/admin_console/policy_settings.jsx @@ -21,12 +21,16 @@ export default class PolicySettings extends AdminSettings { this.renderSettings = this.renderSettings.bind(this); this.state = Object.assign(this.state, { - restrictTeamInvite: props.config.TeamSettings.RestrictTeamInvite + restrictTeamInvite: props.config.TeamSettings.RestrictTeamInvite, + restrictPublicChannelManagement: props.config.TeamSettings.RestrictPublicChannelManagement, + restrictPrivateChannelManagement: props.config.TeamSettings.RestrictPrivateChannelManagement }); } getConfigFromState(config) { config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite; + config.TeamSettings.RestrictPublicChannelManagement = this.state.restrictPublicChannelManagement; + config.TeamSettings.RestrictPrivateChannelManagement = this.state.restrictPrivateChannelManagement; return config; } @@ -48,9 +52,9 @@ export default class PolicySettings extends AdminSettings { } /> + + } + value={this.state.restrictPublicChannelManagement} + onChange={this.handleChange} + helpText={ + + } + /> + + } + value={this.state.restrictPrivateChannelManagement} + onChange={this.handleChange} + helpText={ + + } + /> ); } diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 3449a0fd6..2b9b1e1cc 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -56,6 +56,7 @@ export default class ChannelHeader extends React.Component { state.showRenameChannelModal = false; this.state = state; } + getStateFromStores() { const extraInfo = ChannelStore.getExtraInfo(this.props.channelId); @@ -67,6 +68,7 @@ export default class ChannelHeader extends React.Component { currentUser: UserStore.getCurrentUser() }; } + validState() { if (!this.state.channel || !this.state.memberChannel || @@ -77,6 +79,7 @@ export default class ChannelHeader extends React.Component { } return true; } + componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addExtraInfoChangeListener(this.onListenerChange); @@ -87,6 +90,7 @@ export default class ChannelHeader extends React.Component { $('.sidebar--left .dropdown-menu').perfectScrollbar(); document.addEventListener('keydown', this.openRecentMentions); } + componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); ChannelStore.removeExtraInfoChangeListener(this.onListenerChange); @@ -96,6 +100,7 @@ export default class ChannelHeader extends React.Component { UserStore.removeStatusesChangeListener(this.onListenerChange); document.removeEventListener('keydown', this.openRecentMentions); } + onListenerChange() { const newState = this.getStateFromStores(); if (!Utils.areObjectsEqual(newState, this.state)) { @@ -103,6 +108,7 @@ export default class ChannelHeader extends React.Component { } $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}}); } + handleLeave() { Client.leaveChannel(this.state.channel.id, () => { @@ -119,6 +125,7 @@ export default class ChannelHeader extends React.Component { } ); } + searchMentions(e) { e.preventDefault(); @@ -146,12 +153,14 @@ export default class ChannelHeader extends React.Component { is_mention_search: true }); } + openRecentMentions(e) { if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.keyCode === Constants.KeyCodes.M) { e.preventDefault(); this.searchMentions(e); } } + showRenameChannelModal(e) { e.preventDefault(); @@ -159,6 +168,7 @@ export default class ChannelHeader extends React.Component { showRenameChannelModal: true }); } + hideRenameChannelModal() { this.setState({ showRenameChannelModal: false @@ -179,6 +189,30 @@ export default class ChannelHeader extends React.Component { return null; } + showManagementOptions(channel, isAdmin, isSystemAdmin) { + if (global.window.mm_license.IsLicensed !== 'true') { + return true; + } + + if (channel.type === Constants.OPEN_CHANNEL) { + if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + return false; + } + if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + return false; + } + } else if (channel.type === Constants.PRIVATE_CHANNEL) { + if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + return false; + } + if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + return false; + } + } + + return true; + } + render() { if (!this.validState()) { return null; @@ -210,7 +244,8 @@ export default class ChannelHeader extends React.Component { ); let channelTitle = channel.display_name; const currentId = this.state.currentUser.id; - const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); const isDirect = (this.state.channel.type === 'D'); if (isDirect) { @@ -331,67 +366,90 @@ export default class ChannelHeader extends React.Component { dropdownContents.push(
  • ); - dropdownContents.push( + + const deleteOption = (
  • - this.setState({showEditChannelPurposeModal: true})} + dialogType={DeleteChannelModal} + dialogProps={{channel}} > - -
  • - ); - dropdownContents.push( -
  • - -
  • ); - if (isAdmin) { + if (this.showManagementOptions(channel, isAdmin, isSystemAdmin)) { + dropdownContents.push( +
  • + + + +
  • + ); + + dropdownContents.push( +
  • + this.setState({showEditChannelPurposeModal: true})} + > + + +
  • + ); + dropdownContents.push(
  • - - - -
  • - ); + dropdownContents.push(deleteOption); } + } else if (this.state.userCount === 1) { + dropdownContents.push(deleteOption); } const canLeave = channel.type === Constants.PRIVATE_CHANNEL ? this.state.userCount > 1 : true; diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx index 54a06d0ae..b7ffff712 100644 --- a/webapp/components/more_channels.jsx +++ b/webapp/components/more_channels.jsx @@ -6,8 +6,11 @@ import LoadingScreen from './loading_screen.jsx'; import NewChannelFlow from './new_channel_flow.jsx'; import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Constants from 'utils/constants.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; @@ -132,6 +135,41 @@ export default class MoreChannels extends React.Component { serverError =
    ; } + let createNewChannelButton = ( + + ); + + let createChannelHelpText = ( +

    + +

    + ); + + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + createNewChannelButton = null; + createChannelHelpText = null; + } else if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + createNewChannelButton = null; + createChannelHelpText = null; + } + } + var moreChannels; if (this.state.channels != null) { @@ -153,12 +191,7 @@ export default class MoreChannels extends React.Component { defaultMessage='No more channels to join' />

    -

    - -

    + {createChannelHelpText} ); } @@ -195,16 +228,7 @@ export default class MoreChannels extends React.Component { defaultMessage='More Channels' /> - + {createNewChannelButton}

    {this.props.serverError}

    ; } + let createPublicChannelLink = ( + + + + ); + + let createPrivateChannelLink = ( + + + + ); + + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + createPublicChannelLink = null; + } else if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + createPublicChannelLink = null; + } + + if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + createPrivateChannelLink = null; + } else if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + createPrivateChannelLink = null; + } + } + var channelTerm = ''; var channelSwitchText = ''; switch (this.props.channelType) { @@ -129,15 +174,7 @@ class NewChannelModal extends React.Component { id='channel_modal.privateGroup1' defaultMessage='Create a new private group with restricted membership. ' /> - - - + {createPublicChannelLink} ); break; @@ -154,15 +191,7 @@ class NewChannelModal extends React.Component { id='channel_modal.publicChannel2' defaultMessage='Create a new public channel anyone can join. ' /> - - - + {createPrivateChannelLink} ); break; diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx index 4f678274d..fdcae1dff 100644 --- a/webapp/components/sidebar.jsx +++ b/webapp/components/sidebar.jsx @@ -682,6 +682,55 @@ export default class Sidebar extends React.Component { /> ); + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); + + let createPublicChannelIcon = ( + + + {'+'} + + + ); + + let createPrivateChannelIcon = ( + + + {'+'} + + + ); + + if (global.window.mm_license.IsLicensed === 'true') { + if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + createPublicChannelIcon = null; + } else if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + createPublicChannelIcon = null; + } + + if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { + createPrivateChannelIcon = null; + } else if (global.window.mm_config.RestrictPrivateChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { + createPrivateChannelIcon = null; + } + } + return (
    - - - {'+'} - - + {createPublicChannelIcon} {publicChannelItems} @@ -765,19 +802,7 @@ export default class Sidebar extends React.Component { id='sidebar.pg' defaultMessage='Private Groups' /> - - - {'+'} - - + {createPrivateChannelIcon} {privateChannelItems} diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx index 2cf758f00..25136e8bc 100644 --- a/webapp/components/sidebar_right_menu.jsx +++ b/webapp/components/sidebar_right_menu.jsx @@ -186,10 +186,10 @@ 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) { + if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) { teamLink = null; inviteLink = null; - } else if (global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_TEAM_ADMIN && !isAdmin) { + } else if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) { teamLink = null; inviteLink = null; } diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx index b0d831d96..639fa07b2 100644 --- a/webapp/components/tutorial/tutorial_intro_screens.jsx +++ b/webapp/components/tutorial/tutorial_intro_screens.jsx @@ -108,7 +108,7 @@ export default class TutorialIntroScreens extends React.Component { let inviteModalLink; let inviteText; - if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.RestrictTeamInvite === Constants.TEAM_INVITE_ALL) { + if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_ALL) { if (team.type === Constants.INVITE_TEAM) { inviteModalLink = (