From 320383d8947691196695b88cbe5da3118dd17794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20V=C3=A9lez?= Date: Wed, 29 Apr 2026 10:11:49 +0200 Subject: [PATCH] MM-67326 - add channel settings abac e2e (#36277) --- .../channel_settings_access_control.spec.ts | 515 ++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 e2e-tests/playwright/specs/functional/channels/channel_settings/channel_settings_access_control.spec.ts diff --git a/e2e-tests/playwright/specs/functional/channels/channel_settings/channel_settings_access_control.spec.ts b/e2e-tests/playwright/specs/functional/channels/channel_settings/channel_settings_access_control.spec.ts new file mode 100644 index 00000000000..0bb8d558455 --- /dev/null +++ b/e2e-tests/playwright/specs/functional/channels/channel_settings/channel_settings_access_control.spec.ts @@ -0,0 +1,515 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +/** + * @objective E2E tests for the Access Control tab in Channel Settings Modal + * @reference MM-67326 + */ + +import {ChannelsPage, expect, test} from '@mattermost/playwright-lib'; + +import { + enableABACConfig, + ensureDepartmentAttribute, + createParentPolicy, + assignChannelsToPolicy, + createPrivateChannel, + createPublicChannel, + createGroupConstrainedPrivateChannel, + setUserAttribute, + addAttributeRule, + createTeamAdmin, +} from '../team_settings/helpers'; + +test.describe('Channel Settings Modal - Access Control Tab', () => { + test('MM-67326_c1 Access Control tab visible for admin on private channel with ABAC enabled', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // * Access Control tab is visible + await expect(channelSettings.container.getByTestId('access_rules-tab-button')).toBeVisible(); + + await channelSettings.close(); + }); + + test('MM-67326_c2 Access Control tab hidden when ABAC disabled', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + // ABAC NOT enabled + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // * Access Control tab is NOT visible + await expect(channelSettings.container.getByTestId('access_rules-tab-button')).not.toBeVisible(); + + await channelSettings.close(); + }); + + test('MM-67326_c3 Access Control tab hidden for public channel', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + + const channel = await createPublicChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // * Access Control tab is NOT visible for public channel + await expect(channelSettings.container.getByTestId('access_rules-tab-button')).not.toBeVisible(); + + await channelSettings.close(); + }); + + test('MM-67326_c4 Access Control tab hidden for group-constrained channel', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + + const channel = await createGroupConstrainedPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // * Access Control tab is NOT visible for group-constrained channel + await expect(channelSettings.container.getByTestId('access_rules-tab-button')).not.toBeVisible(); + + await channelSettings.close(); + }); + + test('MM-67326_c5 Auto-add checkbox disabled when no rules defined', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // * Auto-add checkbox is disabled — no rules or system policies defined + const autoAddCheckbox = tab.locator('#autoSyncMembersCheckbox'); + await expect(autoAddCheckbox).toBeDisabled(); + + await channelSettings.close(); + }); + + test('MM-67326_c6 System policy banner visible when parent policy is applied to channel', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + const channel = await createPrivateChannel(adminClient, team.id); + + // # Create a parent (team-level) policy and assign this channel to it + const policy = await createParentPolicy(adminClient, `Parent Policy ${Date.now()}`); + await assignChannelsToPolicy(adminClient, policy.id, [channel.id]); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // * System policy banner is visible + await expect(tab.locator('.ChannelSettingsModal__systemPolicies')).toBeVisible({timeout: 10000}); + + await channelSettings.close(); + }); + + test('MM-67326_c7 Add attribute rule and save without auto-add', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + // # Admin's Department must satisfy the rule (self-exclusion check) + await setUserAttribute(adminClient, adminUser.id, 'Department', 'Engineering'); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add an attribute rule: Department == Engineering + await addAttributeRule(tab, page, 'Engineering'); + + // # Save (auto-add remains off — no confirmation modal for first-time rules with no member changes) + const saveBtn = tab.locator('[data-testid="SaveChangesPanel__save-btn"]'); + await expect(saveBtn).toBeEnabled({timeout: 10000}); + await saveBtn.click(); + + // * SaveChangesPanel disappears — rules were saved + await expect(saveBtn).not.toBeVisible({timeout: 15000}); + + await channelSettings.close(); + }); + + test('MM-67326_c8 Auto-add checkbox becomes enabled after adding an attribute rule', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // * Checkbox starts disabled — no rules or system policies + const autoAddCheckbox = tab.locator('#autoSyncMembersCheckbox'); + await expect(autoAddCheckbox).toBeDisabled(); + + // # Add an attribute rule + await addAttributeRule(tab, page, 'Engineering'); + + // * Checkbox is now enabled + await expect(autoAddCheckbox).toBeEnabled({timeout: 5000}); + + await channelSettings.close(); + }); + + test('MM-67326_c9 Auto-add members: user matching rule is added to channel via sync job', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + // # Admin's Department satisfies the rule (self-exclusion check passes) + await setUserAttribute(adminClient, adminUser.id, 'Department', 'Engineering'); + + // # Private channel — admin is the creator and only member + const channel = await createPrivateChannel(adminClient, team.id); + + // # Target user: in the team, Department=Engineering, NOT yet in the channel + const targetUser = await createTeamAdmin(adminClient, team.id); + await setUserAttribute(adminClient, targetUser.id, 'Department', 'Engineering'); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add attribute rule: Department == Engineering + await addAttributeRule(tab, page, 'Engineering'); + + // # Enable auto-add members + const autoAddCheckbox = tab.locator('#autoSyncMembersCheckbox'); + await expect(autoAddCheckbox).toBeEnabled({timeout: 5000}); + await autoAddCheckbox.click(); + await expect(autoAddCheckbox).toBeChecked(); + + // # Save — confirmation modal appears because targetUser will be added + const saveBtn = tab.locator('[data-testid="SaveChangesPanel__save-btn"]'); + await expect(saveBtn).toBeEnabled({timeout: 10000}); + await saveBtn.click(); + + // # Confirm in the membership changes modal + const confirmModal = page.locator('#channel-access-rules-confirm-modal'); + await confirmModal.waitFor({state: 'visible', timeout: 30000}); + await confirmModal.getByRole('button', {name: /Save and apply/}).click(); + await confirmModal.waitFor({state: 'hidden', timeout: 15000}); + + // * Poll until target user appears as a channel member (sync job runs asynchronously) + let isMember = false; + for (let i = 0; i < 30; i++) { + try { + await adminClient.getChannelMember(channel.id, targetUser.id); + isMember = true; + break; + } catch { + await page.waitForTimeout(1000); + } + } + expect(isMember).toBe(true); + + await channelSettings.close(); + }); + + test('MM-67326_c10 Self-exclusion validation prevents saving rule that excludes current user', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + // # Admin is in Engineering — rule requires Sales, so admin would be excluded + await setUserAttribute(adminClient, adminUser.id, 'Department', 'Engineering'); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add a rule that excludes the admin: Department == Sales + await addAttributeRule(tab, page, 'Sales'); + + // # Attempt to save + const saveBtn = tab.locator('[data-testid="SaveChangesPanel__save-btn"]'); + await expect(saveBtn).toBeEnabled({timeout: 10000}); + await saveBtn.click(); + + // * Self-exclusion error modal appears + const selfExclusionModal = page.locator('#confirmModal').filter({hasText: 'Cannot save access rules'}); + await expect(selfExclusionModal).toBeVisible({timeout: 15000}); + + // # Dismiss the modal to go back to editing + await selfExclusionModal.getByRole('button', {name: 'Back to editing'}).click(); + await expect(selfExclusionModal).not.toBeVisible(); + + await channelSettings.close(); + }); + + test('MM-67326_c11 Unsaved changes — closing modal without saving keeps it open on first click', async ({pw}) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + const channel = await createPrivateChannel(adminClient, team.id); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add a rule to create unsaved changes + await addAttributeRule(tab, page, 'Engineering'); + + // * SaveChangesPanel is visible — there are unsaved changes + await expect(tab.locator('[data-testid="SaveChangesPanel__save-btn"]')).toBeVisible({timeout: 5000}); + + // # First close click — modal stays open (unsaved-changes two-step close) + await channelSettings.closeButton.click(); + await expect(channelSettings.container).toBeVisible({timeout: 3000}); + + // # Second click — modal closes + await channelSettings.closeButton.click(); + await expect(channelSettings.container).not.toBeVisible({timeout: 10000}); + }); + + test('MM-67326_c12 View users — Restricted tab shows member count and user when rule removes a channel member', async ({ + pw, + }) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + // # Admin satisfies the rule + await setUserAttribute(adminClient, adminUser.id, 'Department', 'Engineering'); + + const channel = await createPrivateChannel(adminClient, team.id); + + // # Add a second member who will NOT satisfy the rule (no Department attribute) + const memberToRemove = await createTeamAdmin(adminClient, team.id); + await adminClient.addToChannel(memberToRemove.id, channel.id); + // memberToRemove has no Department set → will not match "Department == Engineering" + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add rule: Department == Engineering (memberToRemove doesn't match → will be removed) + await addAttributeRule(tab, page, 'Engineering'); + + // # Click Save — confirmation modal appears because memberToRemove will be removed + const saveBtn = tab.locator('[data-testid="SaveChangesPanel__save-btn"]'); + await expect(saveBtn).toBeEnabled({timeout: 10000}); + await saveBtn.click(); + + const confirmModal = page.locator('#channel-access-rules-confirm-modal'); + await confirmModal.waitFor({state: 'visible', timeout: 30000}); + + // * Summary message shows 0 users added and 1 member removed + await expect(confirmModal).toContainText('remove 1 current channel member'); + + // # Click "View users" to open the detailed user list + await confirmModal.getByRole('button', {name: 'View users'}).click(); + + // * "Restricted (1)" tab is visible — one member will be removed + await expect(confirmModal.getByRole('button', {name: /Restricted \(1\)/})).toBeVisible({timeout: 5000}); + + // # Switch to the Restricted tab + await confirmModal.getByRole('button', {name: /Restricted \(1\)/}).click(); + + // * memberToRemove's username appears in the restricted list + await expect(confirmModal).toContainText(memberToRemove.username, {timeout: 5000}); + + // # Cancel — don't actually apply + await confirmModal.getByRole('button', {name: 'Cancel'}).click(); + await confirmModal.waitFor({state: 'hidden', timeout: 10000}); + + await channelSettings.close(); + }); + + test('MM-67326_c13 View users — Allowed tab shows member count and user when auto-add brings new members', async ({ + pw, + }) => { + await pw.skipIfNoLicense(); + const {adminUser, adminClient, team} = await pw.initSetup(); + await enableABACConfig(adminClient); + await ensureDepartmentAttribute(adminClient); + + // # Admin satisfies the rule + await setUserAttribute(adminClient, adminUser.id, 'Department', 'Engineering'); + + // # Private channel — admin is the only member + const channel = await createPrivateChannel(adminClient, team.id); + + // # Target user: in the team, Department=Engineering, NOT yet in the channel + const memberToAdd = await createTeamAdmin(adminClient, team.id); + await setUserAttribute(adminClient, memberToAdd.id, 'Department', 'Engineering'); + + const {page} = await pw.testBrowser.login(adminUser); + const channelsPage = new ChannelsPage(page); + await channelsPage.goto(team.name, channel.name); + await channelsPage.toBeVisible(); + + const channelSettings = await channelsPage.openChannelSettings(); + + // # Navigate to Access Control tab + await channelSettings.container.getByTestId('access_rules-tab-button').click(); + + const tab = channelSettings.container.locator('.ChannelSettingsModal__accessRulesTab'); + await expect(tab).toBeVisible({timeout: 10000}); + + // # Add rule: Department == Engineering + await addAttributeRule(tab, page, 'Engineering'); + + // # Enable auto-add so memberToAdd appears in the "to add" list + const autoAddCheckbox = tab.locator('#autoSyncMembersCheckbox'); + await expect(autoAddCheckbox).toBeEnabled({timeout: 5000}); + await autoAddCheckbox.click(); + await expect(autoAddCheckbox).toBeChecked(); + + // # Click Save — confirmation modal appears because memberToAdd will be added + const saveBtn = tab.locator('[data-testid="SaveChangesPanel__save-btn"]'); + await expect(saveBtn).toBeEnabled({timeout: 10000}); + await saveBtn.click(); + + const confirmModal = page.locator('#channel-access-rules-confirm-modal'); + await confirmModal.waitFor({state: 'visible', timeout: 30000}); + + // * Summary shows 0 members removed (no one in this channel lacks the attribute) + await expect(confirmModal).toContainText('remove 0 current channel members'); + + // # Click "View users" to open the detailed user list + // Note: the "add N users" count in the summary is not asserted here because leftover + // users from previous test runs may also have Department=Engineering, making the + // count non-deterministic. We verify the specific user in the Allowed tab instead. + await confirmModal.getByRole('button', {name: 'View users'}).click(); + + // * Allowed tab is visible with at least one user (memberToAdd) + await expect(confirmModal.locator('.ChannelAccessRulesConfirmModal__tab', {hasText: /Allowed/})).toBeVisible({ + timeout: 5000, + }); + + // # The Allowed tab is active by default — verify memberToAdd's username is shown + await expect(confirmModal).toContainText(memberToAdd.username, {timeout: 5000}); + + // # Cancel — don't actually apply + await confirmModal.getByRole('button', {name: 'Cancel'}).click(); + await confirmModal.waitFor({state: 'hidden', timeout: 10000}); + + await channelSettings.close(); + }); +});