diff --git a/server/channels/api4/team.go b/server/channels/api4/team.go
index b6d991d7081..325d6acb8ce 100644
--- a/server/channels/api4/team.go
+++ b/server/channels/api4/team.go
@@ -251,6 +251,12 @@ func patchTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ // if changing "AllowOpenInvite" or "AllowedDomains", user must have InviteUser permission
+ if (team.AllowOpenInvite != nil || team.AllowedDomains != nil) && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionInviteUser) {
+ c.SetPermissionError(model.PermissionInviteUser)
+ return
+ }
+
if oldTeam, err := c.App.GetTeam(c.Params.TeamId); err == nil {
auditRec.AddEventPriorState(oldTeam)
auditRec.AddEventObjectType("team")
diff --git a/server/channels/api4/team_test.go b/server/channels/api4/team_test.go
index 170f43aa8fa..604f73241f7 100644
--- a/server/channels/api4/team_test.go
+++ b/server/channels/api4/team_test.go
@@ -618,6 +618,39 @@ func TestPatchTeam(t *testing.T) {
_, _, err = client.PatchTeam(context.Background(), th.BasicTeam.Id, patch)
require.NoError(t, err)
})
+
+ t.Run("Changing AllowOpenInvite requires InviteUser permission", func(t *testing.T) {
+ th.LoginTeamAdmin()
+ team2 := &model.Team{DisplayName: "Name", Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TeamOpen, AllowOpenInvite: true}
+ team2, _, _ = th.Client.CreateTeam(context.Background(), team2)
+
+ patch2 := &model.TeamPatch{
+ AllowOpenInvite: model.NewPointer(false),
+ AllowedDomains: model.NewPointer("test.com"),
+ }
+
+ rteam2, _, err3 := th.Client.PatchTeam(context.Background(), team2.Id, patch2)
+ require.NoError(t, err3)
+ require.Equal(t, team2.Id, rteam2.Id)
+ require.False(t, rteam2.AllowOpenInvite)
+
+ // remove invite user permission from team admin and user roles
+ th.RemovePermissionFromRole(model.PermissionInviteUser.Id, model.TeamAdminRoleId)
+ th.RemovePermissionFromRole(model.PermissionInviteUser.Id, model.TeamUserRoleId)
+
+ patch2 = &model.TeamPatch{
+ AllowOpenInvite: model.NewPointer(true),
+ }
+
+ _, _, err3 = th.Client.PatchTeam(context.Background(), rteam2.Id, patch2)
+ require.Error(t, err3)
+
+ patch2 = &model.TeamPatch{
+ AllowedDomains: model.NewPointer("testDomain.com"),
+ }
+ _, _, err3 = th.Client.PatchTeam(context.Background(), rteam2.Id, patch2)
+ require.Error(t, err3)
+ })
}
func TestRestoreTeam(t *testing.T) {
diff --git a/webapp/channels/src/components/team_settings_modal/index.ts b/webapp/channels/src/components/team_settings_modal/index.ts
index ecec6c650f7..46e51c41736 100644
--- a/webapp/channels/src/components/team_settings_modal/index.ts
+++ b/webapp/channels/src/components/team_settings_modal/index.ts
@@ -3,6 +3,10 @@
import {connect} from 'react-redux';
+import {Permissions} from 'mattermost-redux/constants';
+import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles';
+import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
+
import {isModalOpen} from 'selectors/views/modals';
import {ModalIdentifiers} from 'utils/constants';
@@ -12,9 +16,12 @@ import type {GlobalState} from 'types/store';
import TeamSettingsModal from './team_settings_modal';
function mapStateToProps(state: GlobalState) {
+ const teamId = getCurrentTeamId(state);
+ const canInviteUsers = haveITeamPermission(state, teamId, Permissions.INVITE_USER);
const modalId = ModalIdentifiers.TEAM_SETTINGS;
return {
show: isModalOpen(state, modalId),
+ canInviteUsers,
};
}
diff --git a/webapp/channels/src/components/team_settings_modal/team_settings_modal.test.tsx b/webapp/channels/src/components/team_settings_modal/team_settings_modal.test.tsx
index 3adb852d37a..6e3a3cb8a40 100644
--- a/webapp/channels/src/components/team_settings_modal/team_settings_modal.test.tsx
+++ b/webapp/channels/src/components/team_settings_modal/team_settings_modal.test.tsx
@@ -10,8 +10,8 @@ import {renderWithContext} from 'tests/react_testing_utils';
describe('components/team_settings_modal', () => {
const baseProps = {
- isCloud: false,
onExited: jest.fn(),
+ canInviteUsers: true,
};
test('should hide the modal when the close button is clicked', async () => {
@@ -25,5 +25,31 @@ describe('components/team_settings_modal', () => {
fireEvent.click(screen.getByText('Close'));
expect(modal.className).toBe('fade modal');
});
+
+ test('should display access tab when can invite users', async () => {
+ const props = {...baseProps, canInviteUsers: true};
+ renderWithContext(
+ ,
+ );
+ const infoButton = screen.getByRole('tab', {name: 'info'});
+ expect(infoButton).toBeDefined();
+ const accessButton = screen.getByRole('tab', {name: 'access'});
+ expect(accessButton).toBeDefined();
+ });
+
+ test('should not display access tab when can not invite users', async () => {
+ const props = {...baseProps, canInviteUsers: false};
+ renderWithContext(
+ ,
+ );
+ const tabs = screen.getAllByRole('tab');
+ expect(tabs.length).toEqual(1);
+ const infoButton = screen.getByRole('tab', {name: 'info'});
+ expect(infoButton).toBeDefined();
+ });
});
diff --git a/webapp/channels/src/components/team_settings_modal/team_settings_modal.tsx b/webapp/channels/src/components/team_settings_modal/team_settings_modal.tsx
index 5a4bc487008..0aa75da164f 100644
--- a/webapp/channels/src/components/team_settings_modal/team_settings_modal.tsx
+++ b/webapp/channels/src/components/team_settings_modal/team_settings_modal.tsx
@@ -12,9 +12,10 @@ const SettingsSidebar = React.lazy(() => import('components/settings_sidebar'));
type Props = {
onExited: () => void;
+ canInviteUsers: boolean;
}
-const TeamSettingsModal = ({onExited}: Props) => {
+const TeamSettingsModal = ({onExited, canInviteUsers}: Props) => {
const [activeTab, setActiveTab] = useState('info');
const [show, setShow] = useState(true);
const [hasChanges, setHasChanges] = useState(false);
@@ -49,8 +50,10 @@ const TeamSettingsModal = ({onExited}: Props) => {
const tabs = [
{name: 'info', uiName: formatMessage({id: 'team_settings_modal.infoTab', defaultMessage: 'Info'}), icon: 'icon icon-information-outline', iconTitle: formatMessage({id: 'generic_icons.info', defaultMessage: 'Info Icon'})},
- {name: 'access', uiName: formatMessage({id: 'team_settings_modal.accessTab', defaultMessage: 'Access'}), icon: 'icon icon-account-multiple-outline', iconTitle: formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})},
];
+ if (canInviteUsers) {
+ tabs.push({name: 'access', uiName: formatMessage({id: 'team_settings_modal.accessTab', defaultMessage: 'Access'}), icon: 'icon icon-account-multiple-outline', iconTitle: formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})});
+ }
return (