mirror of
https://github.com/hanzoai/team.git
synced 2026-11-01 07:55:31 +00:00
* MM-69063 - Add team ABAC model and constants foundation
* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration
* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and GetTeamMembersToRemove store
* Enforce team membership ABAC on join and hide policy governed teams from non-qualifying users in the directory
* Add team_ids to access policy assign/unassign, expose per-team policy GET, and support abac_match_only for not_in_team user listing
* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users
* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings
* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id validation, export, and audit cleanup
* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete
* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts
* Enhance access control policy tests to include team policy search alongside channel policy search
* Add team membership access control feature flag to docker-compose generation
* Implement team access control policy checks and refactor related components
* MM-69100 - add team membership ABAC sync worker with mode-aware removal and auto-add
* Add team ABAC removal/auto-add notifications, cascade audit, and team custom-rules save backend
* Add Team Settings team membership tab with custom-rules editor, auto-add toggle, save confirmation, and mode-flip sync trigger
* Add team access control policy panels, job details team list, and e2e coverage for membership tab and discoverability cards
* ix save double-submit with loading state, team privacy via updateTeamPrivacy, mode-flip count accuracy, sync trigger logic, i18n keys, and e2e test cleanup
* Add team membership policy notices to invite and add-to-team flows, team access control system messages, and a team policy attributes endpoint
* Add team membership recommended tag for qualifying users and team requirements notice in the members modal
* temp
* adjust styling, add new e2e, fix team admin job permissions issues
* e2e clean items created
* fix directory and team integration e2e tests
* Surface team sync results in the Sync Job Details modal via a Teams tab linked from the chained channel job
* code clean up, adjust styling
* Add access control attributes endpoint and enhance E2E tests for team membership
* Enhance team access control by updating permission checks and syncing jobs
* Add API-level enforcement gate tests for ABAC team membership
* Fix formatting in team channel settings component by adding missing semicolon
* Fix formatting in Client4 class by adjusting type annotations for clarity
* revert unwanted changes in package-lock
* Refactor job permission test and simplify error handling in team details component
* Update test assertions and enhance auto-add functionality in team membership tab
* Update onboarding tests to use 'Public Team' card for team access settings
* Refactor AccessSettings and TeamPolicyEditor components to replace allowOpenInviteCheckbox with public and private team buttons, and update access control job dispatching logic.
* Remove redundant state updates for channel assignments in access control policy reducer
* Implement email suppression for team membership notifications and add related tests
* Authorize ABAC team self-join by attribute match instead of the join_private_teams role
* comments clean up
* Gate team privacy type normalization behind active ABAC, keep legacy allow_open_invite-only path otherwise
* adjust job details styles
* Add team membership policy disconnect confirmation, fix linked-policy affected-member count, and polish job details modal
* Combine custom rules with system policy expressions for accurate confirm counts
* Implement child resource count stamping and deletion gating for access control policies
* Enhance team sync job triggering logic for membership changes without auto-add
* Refactor team privacy handling to align with open-directory model; update comments for clarity on allow_open_invite logic.
* Deduplicate team ABAC sync jobs per policy to prevent concurrent runs emitting duplicate DMs and audit records on HA clusters
* fix linter warnings
* Add confirmation for policy removal in TeamDetails tests
* Update delete policy message to include Teams in the warning
* Add linked teams warning to policy deletion and update translations
* Add Auto-add feature for policy selection and team management
- Enhanced PolicySelectionModal to include an Auto-add checkbox for each policy.
- Updated PolicyList to manage Auto-add state and reflect changes in the UI.
- Modified TeamAccessControl to handle Auto-add functionality and persist changes.
- Added tests to ensure Auto-add behavior works as expected in various scenarios.
- Updated translations for Auto-add related strings.
* Add team membership sync footer and enhance job fetching with policy ID
* Refactor team membership policy handling and sync footer implementation
* Add team sync channel cascade test for private channel membership
---------
(cherry picked from commit a582f03570)
Co-authored-by: Pablo Vélez <[email protected]>
351 lines
13 KiB
JavaScript
351 lines
13 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||
// See LICENSE.txt for license information.
|
||
|
||
// ***************************************************************
|
||
// - [#] indicates a test step (e.g. # Go to a page)
|
||
// - [*] indicates an assertion (e.g. * Check the title)
|
||
// - Use element ID when selecting an element. Create one if none.
|
||
// ***************************************************************
|
||
|
||
// Stage: @prod
|
||
// Group: @channels @team_settings
|
||
|
||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||
import {getAdminAccount} from '@/support/env';
|
||
|
||
describe('Teams Suite', () => {
|
||
let testTeam;
|
||
let testUser;
|
||
let newUser;
|
||
|
||
beforeEach(() => {
|
||
cy.apiAdminLogin();
|
||
cy.apiCreateUser().then(({user}) => {
|
||
newUser = user;
|
||
});
|
||
cy.apiInitSetup().then(({team, user}) => {
|
||
testTeam = team;
|
||
testUser = user;
|
||
});
|
||
});
|
||
|
||
it('MM-T393 Cancel out of leaving team', () => {
|
||
// # Login and go to /
|
||
cy.apiLogin(testUser);
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// * Check the team name
|
||
cy.uiGetLHSHeader().findByText(testTeam.display_name);
|
||
|
||
// * Check the initial Url
|
||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Manage Members"
|
||
cy.uiOpenTeamMenu('Leave team');
|
||
|
||
// * Check that the "leave team modal" opened up
|
||
cy.get('#leaveTeamModal').should('be.visible');
|
||
|
||
// # Click on no
|
||
cy.get('#leaveTeamNo').click();
|
||
|
||
// * Check that the "leave team modal" closed
|
||
cy.get('#leaveTeamModal').should('not.exist');
|
||
|
||
// * check the team name
|
||
cy.uiGetLHSHeader().findByText(testTeam.display_name);
|
||
|
||
// * check the final Url
|
||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||
});
|
||
|
||
it('MM-T2340 Team or System Admin searches and adds new team member', () => {
|
||
// # Update config
|
||
cy.apiUpdateConfig({
|
||
GuestAccountsSettings: {
|
||
Enable: false,
|
||
},
|
||
});
|
||
|
||
let otherUser;
|
||
cy.apiCreateUser().then(({user}) => {
|
||
otherUser = user;
|
||
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Invite People"
|
||
cy.uiOpenTeamMenu();
|
||
cy.uiGetLHSTeamMenu().find('div.label-elements > span:nth-child(2)').should('have.text', 'Add or invite people to the team');
|
||
cy.uiGetLHSTeamMenu().find("li span:contains('Invite people')").should('have.text', 'Invite people').click().wait(TIMEOUTS.HALF_SEC);
|
||
|
||
// * Check that the Invitation Modal opened up
|
||
cy.findByTestId('invitationModal', {timeout: TIMEOUTS.HALF_SEC}).should('be.visible');
|
||
|
||
cy.get('.users-emails-input__control').should('be.visible').within(($el) => {
|
||
// # Type the first letters of a user
|
||
cy.wrap($el).get('input').typeWithForce(otherUser.first_name);
|
||
});
|
||
|
||
// * Verify user is on the list, then select by clicking on it
|
||
cy.get('.users-emails-input__menu').
|
||
children().should('have.length', 1).
|
||
eq(0).should('contain', `@${otherUser.username}`).and('contain', `${otherUser.first_name} ${otherUser.last_name}`).
|
||
click();
|
||
|
||
// # Click "Invite Members" button, then "Done" button
|
||
cy.findByTestId('inviteButton').click();
|
||
cy.findByTestId('confirm-done').click();
|
||
|
||
// * As sysadmin, verify system message posts in Town Square and Off-Topic
|
||
cy.getLastPost().wait(TIMEOUTS.HALF_SEC).then(($el) => {
|
||
cy.wrap($el).get('.user-popover').
|
||
should('be.visible').
|
||
and('have.text', 'System');
|
||
cy.wrap($el).get('.post-message__text-container').
|
||
should('be.visible').
|
||
and('contain', `You and @${testUser.username} joined the team.`).
|
||
and('contain', `@${otherUser.username} added to the team by you.`);
|
||
});
|
||
|
||
cy.get('#sidebarItem_off-topic').should('be.visible').click({force: true});
|
||
cy.getLastPost().wait(TIMEOUTS.HALF_SEC).then(($el) => {
|
||
cy.wrap($el).get('.user-popover').
|
||
should('be.visible').
|
||
and('have.text', 'System');
|
||
cy.wrap($el).get('.post-message__text-container').
|
||
should('be.visible').
|
||
and('contain', `You and @${testUser.username} joined the channel.`).
|
||
and('contain', `@${otherUser.username} added to the channel by you.`);
|
||
});
|
||
|
||
// # Login as user added to Team and reload
|
||
cy.apiLogin(otherUser);
|
||
|
||
// # Visit the new team and verify that it's in the correct team view
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
cy.uiGetLHSHeader().findByText(testTeam.display_name);
|
||
|
||
const sysadmin = getAdminAccount();
|
||
|
||
// * As other user, verify system message posts in Town Square and Off-Topic
|
||
cy.getLastPost().wait(TIMEOUTS.HALF_SEC).then(($el) => {
|
||
cy.wrap($el).get('.user-popover').
|
||
should('be.visible').
|
||
and('have.text', 'System');
|
||
cy.wrap($el).get('.post-message__text-container').
|
||
should('be.visible').
|
||
and('contain', `@${sysadmin.username} and @${testUser.username} joined the team.`).
|
||
and('contain', `You were added to the team by @${sysadmin.username}.`);
|
||
});
|
||
|
||
cy.get('#sidebarItem_off-topic').should('be.visible').click({force: true});
|
||
|
||
cy.findAllByTestId('postView').last().scrollIntoView();
|
||
|
||
cy.getLastPost().wait(TIMEOUTS.HALF_SEC).then(($el) => {
|
||
cy.wrap($el).get('.user-popover').
|
||
should('be.visible').
|
||
and('have.text', 'System');
|
||
cy.wrap($el).get('.post-message__text-container').
|
||
should('be.visible').
|
||
and('contain', `@${sysadmin.username} and @${testUser.username} joined the channel.`).
|
||
and('contain', `You were added to the channel by @${sysadmin.username}.`);
|
||
});
|
||
|
||
// # Remove user from team
|
||
removeTeamMember(testTeam.name, otherUser.username);
|
||
});
|
||
});
|
||
|
||
it('MM-T394 Leave team by clicking Yes, leave all teams', () => {
|
||
cy.apiUpdateConfig({EmailSettings: {RequireEmailVerification: false}});
|
||
|
||
// // # Login as test user
|
||
cy.apiLogin(testUser);
|
||
|
||
// # Leave all teams
|
||
cy.apiGetTeamsForUser().then(({teams}) => {
|
||
teams.forEach((team) => {
|
||
cy.visit(`/${team.name}/channels/town-square`);
|
||
cy.uiGetLHSHeader().findByText(testTeam.display_name);
|
||
cy.leaveTeam();
|
||
});
|
||
});
|
||
|
||
// * Check that it redirects into team selection page after leaving all teams
|
||
cy.url().should('include', '/select_team');
|
||
|
||
// # Check that logout is visible and then click to logout
|
||
cy.get('#logout').should('be.visible').click();
|
||
|
||
// * Ensure user is logged out
|
||
cy.url({timeout: TIMEOUTS.HALF_MIN}).should('include', 'login');
|
||
});
|
||
|
||
it('MM-T1535 Team setting / Invite code text', () => {
|
||
// # visit /
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// # Go to Access section
|
||
cy.get('#accessButton').click();
|
||
|
||
// # Open edit settings for invite code
|
||
cy.findByText('Invite Code').should('be.visible').click();
|
||
|
||
// * Verify invite code help text is visible
|
||
cy.findByText('The Invite Code is part of the unique team invitation link which is sent to members you’re inviting to this team. Regenerating the code creates a new invitation link and invalidates the previous link.').
|
||
scrollIntoView().
|
||
should('be.visible');
|
||
|
||
// # Close the team settings
|
||
cy.get('body').typeWithForce('{esc}');
|
||
});
|
||
|
||
it('MM-T2312 Team setting / Team name: Change name', () => {
|
||
const teamName = 'Test Team';
|
||
|
||
// # Visit town-square channel
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// # Click on the team name menu item
|
||
cy.findByText('Team Name').should('be.visible').click();
|
||
|
||
// # Change team name in the input
|
||
cy.get('#teamName').should('be.visible').clear().type(teamName);
|
||
|
||
// Save new team name annd close<
|
||
cy.uiSaveAndClose();
|
||
|
||
// Team display name shows as "Testing Team" at top of team menu
|
||
cy.uiGetLHSHeader().findByText(teamName);
|
||
|
||
// Team initials show in the team icon in the sidebar
|
||
cy.get(`#${testTeam.name}TeamButton`).scrollIntoView().should('have.attr', 'aria-label', 'test team team');
|
||
|
||
// # Verify url has original team name
|
||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||
});
|
||
|
||
it('MM-T2317 Team setting / Update team description', () => {
|
||
const teamDescription = 'This is the best team';
|
||
|
||
// # Visit town-square channel
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// # Change team description in the input
|
||
cy.get('#teamDescription').should('be.visible').clear().type(teamDescription);
|
||
cy.get('#teamDescription').should('have.value', teamDescription);
|
||
|
||
// Save and close
|
||
cy.uiSaveAndClose();
|
||
|
||
cy.wait(TIMEOUTS.ONE_HUNDRED_MILLIS);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// * Verify team description is updated
|
||
cy.get('#teamDescription').should('have.text', teamDescription);
|
||
});
|
||
|
||
it('MM-T2318 Allow anyone to join this team', () => {
|
||
// # Visit town-square channel
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// # Go to Access section
|
||
cy.get('#accessButton').click();
|
||
|
||
// # Click 'Public Team' card to allow any user to join this team
|
||
cy.get('#public-private-selector-button-O').should('exist').and('not.have.class', 'selected').click();
|
||
|
||
// # Save and close
|
||
cy.uiSaveAndClose();
|
||
|
||
// # Login as new user
|
||
cy.apiLogin(newUser);
|
||
cy.visit('/');
|
||
|
||
// * Verify if the user is redirected to the Select Team page
|
||
cy.url().should('include', '/select_team');
|
||
cy.get('#teamsYouCanJoinContent').should('be.visible');
|
||
|
||
// # Select test team name
|
||
cy.findByText(testTeam.display_name, {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').click();
|
||
|
||
// # Verify Town square is visible
|
||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||
cy.get('h2.channel-intro__title').should('be.visible').should('have.text', 'Town Square');
|
||
});
|
||
|
||
it('MM-T2322 Do not allow anyone to join this team', () => {
|
||
// # Allow any user with an account on this server to join this new team
|
||
cy.apiCreateTeam().then(({team: joinableTeam}) => {
|
||
cy.apiPatchTeam(joinableTeam.id, {allow_open_invite: true});
|
||
});
|
||
|
||
// # Visit town-square channel
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Team Settings"
|
||
cy.uiOpenTeamMenu('Team settings');
|
||
|
||
// # Go to Access section
|
||
cy.get('#accessButton').click();
|
||
|
||
// * Verify Private Team card is selected (open joining disabled by default)
|
||
cy.get('#public-private-selector-button-P').should('exist').and('have.class', 'selected');
|
||
|
||
// # Save and close
|
||
cy.uiClose();
|
||
|
||
// # Login as new user
|
||
cy.apiLogin(testUser);
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "Join Another Team"
|
||
cy.uiOpenTeamMenu('Join another team');
|
||
|
||
// # Verify the original test team isn't on the list
|
||
cy.get('.signup-team-dir').children().should('not.contain', `#${testTeam.name.charAt(0).toUpperCase() + testTeam.name.slice(1)}`);
|
||
});
|
||
|
||
it('MM-T2328 Member can view and search for members', () => {
|
||
cy.apiLogout();
|
||
cy.wait(TIMEOUTS.ONE_SEC);
|
||
|
||
// * Login as test user and join town square
|
||
cy.apiLogin(testUser);
|
||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||
|
||
// # Open team menu and click "View Members"
|
||
cy.uiOpenTeamMenu('View members');
|
||
cy.wait(TIMEOUTS.HALF_SEC);
|
||
cy.get('#searchUsersInput').should('be.visible').type('sysadmin');
|
||
cy.get('.more-modal__list').should('be.visible').children().should('have.length', 1);
|
||
});
|
||
});
|
||
|
||
function removeTeamMember(teamName, username) {
|
||
cy.apiAdminLogin();
|
||
cy.visit(`/${teamName}`);
|
||
|
||
// # Open team menu and click "Manage Members"
|
||
cy.uiOpenTeamMenu('Manage members');
|
||
|
||
cy.get(`#teamMembersDropdown_${username}`).should('be.visible').click();
|
||
cy.get('#removeFromTeam').should('be.visible').click();
|
||
cy.uiClose();
|
||
}
|