MM-64428 - user tag invite filtering (#31226)
* MM-64428 - user tag invite filtering * fix lint issues * remove unnecesary line * update translations and skip mysql tests * simplify the solution so in abac channels the invitation link is never shown * finish clean up of unnecessary code * clean up and remove no longer necessary translations * remove leftover props and remove no longer needed tests after simplification --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -1260,6 +1260,7 @@ func (a *App) filterOutOfChannelMentions(c request.CTX, sender *model.User, post
|
|||||||
// Differentiate between mentionedUsersInTheTeam who can and can't be added to the channel
|
// Differentiate between mentionedUsersInTheTeam who can and can't be added to the channel
|
||||||
var outOfChannelUsers model.UserSlice
|
var outOfChannelUsers model.UserSlice
|
||||||
var outOfGroupsUsers model.UserSlice
|
var outOfGroupsUsers model.UserSlice
|
||||||
|
|
||||||
if channel.IsGroupConstrained() {
|
if channel.IsGroupConstrained() {
|
||||||
nonMemberIDs, err := a.FilterNonGroupChannelMembers(teamUsers.IDs(), channel)
|
nonMemberIDs, err := a.FilterNonGroupChannelMembers(teamUsers.IDs(), channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1289,6 +1290,8 @@ func makeOutOfChannelMentionPost(sender *model.User, post *model.Post, outOfChan
|
|||||||
|
|
||||||
ephemeralPostId := model.NewId()
|
ephemeralPostId := model.NewId()
|
||||||
var message string
|
var message string
|
||||||
|
|
||||||
|
// Generate message for users who can be invited
|
||||||
if len(outOfChannelUsers) == 1 {
|
if len(outOfChannelUsers) == 1 {
|
||||||
message = T("api.post.check_for_out_of_channel_mentions.message.one", map[string]any{
|
message = T("api.post.check_for_out_of_channel_mentions.message.one", map[string]any{
|
||||||
"Username": ocUsernames[0],
|
"Username": ocUsernames[0],
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, empty
|
|||||||
|
|
||||||
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 1`] = `
|
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<p
|
||||||
|
key="invitable"
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
@@ -67,7 +69,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, more t
|
|||||||
|
|
||||||
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 2`] = `
|
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 2`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<p
|
||||||
|
key="invitable"
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
@@ -129,7 +133,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, more t
|
|||||||
|
|
||||||
exports[`components/post_view/PostAddChannelMember should match snapshot, private channel 1`] = `
|
exports[`components/post_view/PostAddChannelMember should match snapshot, private channel 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<p
|
||||||
|
key="invitable"
|
||||||
|
>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
mentionName="username_1"
|
mentionName="username_1"
|
||||||
@@ -158,7 +164,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, privat
|
|||||||
|
|
||||||
exports[`components/post_view/PostAddChannelMember should match snapshot, public channel 1`] = `
|
exports[`components/post_view/PostAddChannelMember should match snapshot, public channel 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<p
|
||||||
|
key="invitable"
|
||||||
|
>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
mentionName="username_1"
|
mentionName="username_1"
|
||||||
@@ -185,9 +193,45 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, public
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`components/post_view/PostAddChannelMember should match snapshot, with ABAC policy enforced 1`] = `
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
<Connect(Component)
|
||||||
|
channelId="channel_id"
|
||||||
|
key="username_1"
|
||||||
|
mentionName="username_1"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
,
|
||||||
|
</span>
|
||||||
|
<Connect(Component)
|
||||||
|
channelId="channel_id"
|
||||||
|
key="username_2"
|
||||||
|
mentionName="username_2"
|
||||||
|
/>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage=" and "
|
||||||
|
id="post_body.check_for_out_of_channel_mentions.link.and"
|
||||||
|
key="2"
|
||||||
|
/>
|
||||||
|
<Connect(Component)
|
||||||
|
channelId="channel_id"
|
||||||
|
key="username_3"
|
||||||
|
mentionName="username_3"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
did not get notified by this mention because they are not in the channel.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`components/post_view/PostAddChannelMember should match snapshot, with no-groups usernames 1`] = `
|
exports[`components/post_view/PostAddChannelMember should match snapshot, with no-groups usernames 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<p
|
||||||
|
key="invitable"
|
||||||
|
>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
mentionName="username_1"
|
mentionName="username_1"
|
||||||
@@ -211,7 +255,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, with n
|
|||||||
id="post_body.check_for_out_of_channel_mentions.message_last"
|
id="post_body.check_for_out_of_channel_mentions.message_last"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p
|
||||||
|
key="out-of-groups"
|
||||||
|
>
|
||||||
<Connect(Component)
|
<Connect(Component)
|
||||||
channelId="channel_id"
|
channelId="channel_id"
|
||||||
mentionName="user_id_2"
|
mentionName="user_id_2"
|
||||||
|
|||||||
@@ -17,15 +17,18 @@ import PostAddChannelMember from './post_add_channel_member';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
postId: string;
|
postId: string;
|
||||||
|
userIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||||
const post = getPost(state, ownProps.postId) || {};
|
const post = getPost(state, ownProps.postId) || {};
|
||||||
let channelType = '';
|
let channelType = '';
|
||||||
|
let isPolicyEnforced = false;
|
||||||
if (post && post.channel_id) {
|
if (post && post.channel_id) {
|
||||||
const channel = getChannel(state, post.channel_id);
|
const channel = getChannel(state, post.channel_id);
|
||||||
if (channel && channel.type) {
|
if (channel && channel.type) {
|
||||||
channelType = channel.type;
|
channelType = channel.type;
|
||||||
|
isPolicyEnforced = Boolean(channel.policy_enforced);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +36,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
|||||||
channelType,
|
channelType,
|
||||||
currentUser: getCurrentUser(state),
|
currentUser: getCurrentUser(state),
|
||||||
post,
|
post,
|
||||||
|
isPolicyEnforced,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ describe('components/post_view/PostAddChannelMember', () => {
|
|||||||
addChannelMember: jest.fn(),
|
addChannelMember: jest.fn(),
|
||||||
},
|
},
|
||||||
noGroupsUsernames: [],
|
noGroupsUsernames: [],
|
||||||
|
isPolicyEnforced: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should match snapshot, empty postId', () => {
|
test('should match snapshot, empty postId', () => {
|
||||||
@@ -140,4 +141,39 @@ describe('components/post_view/PostAddChannelMember', () => {
|
|||||||
const wrapper = shallow(<PostAddChannelMember {...props}/>);
|
const wrapper = shallow(<PostAddChannelMember {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should match snapshot, with ABAC policy enforced', () => {
|
||||||
|
const props: Props = {
|
||||||
|
...requiredProps,
|
||||||
|
usernames: ['username_1', 'username_2', 'username_3'],
|
||||||
|
isPolicyEnforced: true,
|
||||||
|
};
|
||||||
|
const wrapper = shallow(<PostAddChannelMember {...props}/>);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should never show invite links when policy is enforced (ABAC channels)', () => {
|
||||||
|
const props: Props = {
|
||||||
|
...requiredProps,
|
||||||
|
usernames: ['username_1', 'username_2'],
|
||||||
|
noGroupsUsernames: [],
|
||||||
|
isPolicyEnforced: true,
|
||||||
|
};
|
||||||
|
const wrapper = shallow(<PostAddChannelMember {...props}/>);
|
||||||
|
expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show single consolidated message for ABAC channels regardless of user types', () => {
|
||||||
|
const props: Props = {
|
||||||
|
...requiredProps,
|
||||||
|
usernames: ['user1', 'user2'],
|
||||||
|
noGroupsUsernames: ['user3'],
|
||||||
|
isPolicyEnforced: true,
|
||||||
|
};
|
||||||
|
const wrapper = shallow(<PostAddChannelMember {...props}/>);
|
||||||
|
|
||||||
|
// Should render only one consolidated message with no invite links
|
||||||
|
expect(wrapper.find('p')).toHaveLength(1);
|
||||||
|
expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface Props {
|
|||||||
userIds: string[];
|
userIds: string[];
|
||||||
usernames: string[];
|
usernames: string[];
|
||||||
noGroupsUsernames: string[];
|
noGroupsUsernames: string[];
|
||||||
|
isPolicyEnforced: boolean;
|
||||||
actions: Actions;
|
actions: Actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +147,33 @@ export default class PostAddChannelMember extends React.PureComponent<Props, Sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {channelType, postId, usernames, noGroupsUsernames} = this.props;
|
const {channelType, postId, usernames, noGroupsUsernames, isPolicyEnforced} = this.props;
|
||||||
if (!postId || !channelType) {
|
if (!postId || !channelType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For ABAC channels (policy enforced), NEVER show invite links - only show notification message
|
||||||
|
if (isPolicyEnforced) {
|
||||||
|
// Combine all users into a single message without any invite functionality
|
||||||
|
const allUsers = [...usernames, ...noGroupsUsernames];
|
||||||
|
const allUsersAtMentions = this.generateAtMentions(allUsers);
|
||||||
|
|
||||||
|
if (allUsers.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageText = 'did not get notified by this mention because they are not in the channel.';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
{allUsersAtMentions}
|
||||||
|
{' '}
|
||||||
|
{messageText}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular flow for non-ABAC channels
|
||||||
let link;
|
let link;
|
||||||
if (channelType === Constants.PRIVATE_CHANNEL) {
|
if (channelType === Constants.PRIVATE_CHANNEL) {
|
||||||
link = (
|
link = (
|
||||||
@@ -168,44 +191,32 @@ export default class PostAddChannelMember extends React.PureComponent<Props, Sta
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let outOfChannelMessagePart;
|
// Separate invitable users from group-constrained users
|
||||||
const outOfChannelAtMentions = this.generateAtMentions(usernames);
|
const invitableUsers = usernames.filter((username) => !noGroupsUsernames.includes(username));
|
||||||
if (usernames.length === 1) {
|
const outOfGroupsUsers = noGroupsUsernames;
|
||||||
outOfChannelMessagePart = (
|
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
// Handle invitable users with invite functionality
|
||||||
|
if (invitableUsers.length > 0) {
|
||||||
|
const invitableAtMentions = this.generateAtMentions(invitableUsers);
|
||||||
|
const invitableMessagePart = invitableUsers.length === 1 ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='post_body.check_for_out_of_channel_mentions.message.one'
|
id='post_body.check_for_out_of_channel_mentions.message.one'
|
||||||
defaultMessage='did not get notified by this mention because they are not in the channel. Would you like to '
|
defaultMessage='did not get notified by this mention because they are not in the channel. Would you like to '
|
||||||
/>
|
/>
|
||||||
);
|
) : (
|
||||||
} else if (usernames.length > 1) {
|
|
||||||
outOfChannelMessagePart = (
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='post_body.check_for_out_of_channel_mentions.message.multiple'
|
id='post_body.check_for_out_of_channel_mentions.message.multiple'
|
||||||
defaultMessage='did not get notified by this mention because they are not in the channel. Would you like to '
|
defaultMessage='did not get notified by this mention because they are not in the channel. Would you like to '
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let outOfGroupsMessagePart;
|
messages.push(
|
||||||
const outOfGroupsAtMentions = this.generateAtMentions(noGroupsUsernames);
|
<p key='invitable'>
|
||||||
if (noGroupsUsernames.length) {
|
{invitableAtMentions}
|
||||||
outOfGroupsMessagePart = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='post_body.check_for_out_of_channel_groups_mentions.message'
|
|
||||||
defaultMessage='did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let outOfChannelMessage = null;
|
|
||||||
let outOfGroupsMessage = null;
|
|
||||||
|
|
||||||
if (usernames.length) {
|
|
||||||
outOfChannelMessage = (
|
|
||||||
<p>
|
|
||||||
{outOfChannelAtMentions}
|
|
||||||
{' '}
|
{' '}
|
||||||
{outOfChannelMessagePart}
|
{invitableMessagePart}
|
||||||
<a
|
<a
|
||||||
className='PostBody_addChannelMemberLink'
|
className='PostBody_addChannelMemberLink'
|
||||||
onClick={this.handleAddChannelMember}
|
onClick={this.handleAddChannelMember}
|
||||||
@@ -213,27 +224,35 @@ export default class PostAddChannelMember extends React.PureComponent<Props, Sta
|
|||||||
{link}
|
{link}
|
||||||
</a>
|
</a>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={'post_body.check_for_out_of_channel_mentions.message_last'}
|
id='post_body.check_for_out_of_channel_mentions.message_last'
|
||||||
defaultMessage={'? They will have access to all message history.'}
|
defaultMessage='? They will have access to all message history.'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noGroupsUsernames.length) {
|
// Handle users not in required groups with specific messaging
|
||||||
outOfGroupsMessage = (
|
if (outOfGroupsUsers.length > 0) {
|
||||||
<p>
|
const outOfGroupsAtMentions = this.generateAtMentions(outOfGroupsUsers);
|
||||||
|
const outOfGroupsMessagePart = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='post_body.check_for_out_of_channel_groups_mentions.message'
|
||||||
|
defaultMessage='did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
<p key='out-of-groups'>
|
||||||
{outOfGroupsAtMentions}
|
{outOfGroupsAtMentions}
|
||||||
{' '}
|
{' '}
|
||||||
{outOfGroupsMessagePart}
|
{outOfGroupsMessagePart}
|
||||||
</p>
|
</p>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{outOfChannelMessage}
|
{messages}
|
||||||
{outOfGroupsMessage}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user