* Fix MM-61710

* Fix test

* Make more secure the secureGetFromRecord

* Revert changes related to redux posts

* Move segureGetFromRecord

* Fix test

* Use hasOwn

* Address feedback

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Daniel Espino García
2024-11-27 17:13:22 +01:00
коммит произвёл GitHub
родитель 99f242527e
Коммит 25ff7a3779
10 изменённых файлов: 53 добавлений и 24 удалений

Просмотреть файл

@@ -9,6 +9,7 @@ import type {UserProfile} from '@mattermost/types/users';
import {Posts} from 'mattermost-redux/constants'; import {Posts} from 'mattermost-redux/constants';
import type {MessageData} from 'mattermost-redux/utils/post_list'; import type {MessageData} from 'mattermost-redux/utils/post_list';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import Markdown from 'components/markdown'; import Markdown from 'components/markdown';
@@ -264,7 +265,8 @@ export class CombinedSystemMessage extends React.PureComponent<Props> {
return userId !== currentUserId && userId !== currentUsername; return userId !== currentUserId && userId !== currentUsername;
}). }).
map((userId) => { map((userId) => {
return allUsernames[userId] ? `@${allUsernames[userId]}` : someone; const username = secureGetFromRecord(allUsernames, userId);
return username ? `@${username}` : someone;
}). }).
filter((username) => { filter((username) => {
return username && username !== ''; return username && username !== '';
@@ -299,11 +301,16 @@ export class CombinedSystemMessage extends React.PureComponent<Props> {
singleline: true, singleline: true,
}; };
const selectedPostTypeMessage = secureGetFromRecord(postTypeMessage, postType);
if (!selectedPostTypeMessage) {
return <></>;
}
if (numOthers > 1) { if (numOthers > 1) {
return ( return (
<LastUsers <LastUsers
actor={actor} actor={actor}
expandedLocale={postTypeMessage[postType].many_expanded} expandedLocale={selectedPostTypeMessage.many_expanded}
formatOptions={options} formatOptions={options}
postType={postType} postType={postType}
usernames={usernames} usernames={usernames}
@@ -313,16 +320,16 @@ export class CombinedSystemMessage extends React.PureComponent<Props> {
let localeHolder: MessageDescriptor = {}; let localeHolder: MessageDescriptor = {};
if (numOthers === 0) { if (numOthers === 0) {
localeHolder = postTypeMessage[postType].one; localeHolder = selectedPostTypeMessage.one;
if ( if (
(userIds[0] === this.props.currentUserId || userIds[0] === this.props.currentUsername) && (userIds[0] === this.props.currentUserId || userIds[0] === this.props.currentUsername) &&
postTypeMessage[postType].one_you selectedPostTypeMessage.one_you
) { ) {
localeHolder = postTypeMessage[postType].one_you; localeHolder = selectedPostTypeMessage.one_you;
} }
} else if (numOthers === 1) { } else if (numOthers === 1) {
localeHolder = postTypeMessage[postType].two; localeHolder = selectedPostTypeMessage.two;
} }
const formattedMessage = formatMessage(localeHolder, {firstUser, secondUser, actor}); const formattedMessage = formatMessage(localeHolder, {firstUser, secondUser, actor});

Просмотреть файл

@@ -6,6 +6,7 @@ import {defineMessages, injectIntl} from 'react-intl';
import type {IntlShape, MessageDescriptor} from 'react-intl'; import type {IntlShape, MessageDescriptor} from 'react-intl';
import {Posts} from 'mattermost-redux/constants'; import {Posts} from 'mattermost-redux/constants';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import Markdown from 'components/markdown'; import Markdown from 'components/markdown';
@@ -121,10 +122,12 @@ export class LastUsers extends React.PureComponent<Props, State> {
{numOthers: lastIndex}, {numOthers: lastIndex},
); );
const actorMessage = formatMessage( const selectedTypeMessage = secureGetFromRecord(typeMessage, postType);
{id: typeMessage[postType].id, defaultMessage: typeMessage[postType].defaultMessage},
const actorMessage = selectedTypeMessage ? formatMessage(
{id: selectedTypeMessage.id, defaultMessage: selectedTypeMessage.defaultMessage},
{actor}, {actor},
); ) : '';
return ( return (
<span> <span>

Просмотреть файл

@@ -7,6 +7,7 @@ import styled, {css} from 'styled-components';
import type {PostAction, PostActionOption} from '@mattermost/types/integration_actions'; import type {PostAction, PostActionOption} from '@mattermost/types/integration_actions';
import type {Theme} from 'mattermost-redux/selectors/entities/preferences'; import type {Theme} from 'mattermost-redux/selectors/entities/preferences';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import {changeOpacity} from 'mattermost-redux/utils/theme_utils'; import {changeOpacity} from 'mattermost-redux/utils/theme_utils';
import Markdown from 'components/markdown'; import Markdown from 'components/markdown';
@@ -51,8 +52,8 @@ const ActionButton = ({
if (action.style) { if (action.style) {
const STATUS_COLORS = getStatusColors(theme); const STATUS_COLORS = getStatusColors(theme);
hexColor = hexColor =
STATUS_COLORS[action.style] || secureGetFromRecord(STATUS_COLORS, action.style) ||
theme[action.style] || secureGetFromRecord(theme, action.style) ||
(action.style.match('^#(?:[0-9a-fA-F]{3}){1,2}$') && action.style); (action.style.match('^#(?:[0-9a-fA-F]{3}){1,2}$') && action.style);
} }

Просмотреть файл

@@ -6,6 +6,8 @@ import type {ConnectedProps} from 'react-redux';
import type {PostAction} from '@mattermost/types/integration_actions'; import type {PostAction} from '@mattermost/types/integration_actions';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import {autocompleteChannels} from 'actions/channel_actions'; import {autocompleteChannels} from 'actions/channel_actions';
import {autocompleteUsers} from 'actions/user_actions'; import {autocompleteUsers} from 'actions/user_actions';
import {selectAttachmentMenuAction} from 'actions/views/posts'; import {selectAttachmentMenuAction} from 'actions/views/posts';
@@ -22,7 +24,7 @@ export type OwnProps = {
function mapStateToProps(state: GlobalState, ownProps: OwnProps) { function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
const actions = state.views.posts.menuActions[ownProps.postId]; const actions = state.views.posts.menuActions[ownProps.postId];
const selected = (ownProps.action && ownProps.action.id) ? actions && actions[ownProps.action && ownProps.action.id] : undefined; const selected = (ownProps.action?.id) ? secureGetFromRecord(actions, ownProps.action.id) : undefined;
return { return {
selected, selected,

Просмотреть файл

@@ -12,6 +12,7 @@ import type {
import type {PostImage} from '@mattermost/types/posts'; import type {PostImage} from '@mattermost/types/posts';
import type {ActionResult} from 'mattermost-redux/types/actions'; import type {ActionResult} from 'mattermost-redux/types/actions';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
@@ -101,7 +102,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
if (!attachment.thumb_url) { if (!attachment.thumb_url) {
return; return;
} }
if (!this.props.imagesMetadata || (this.props.imagesMetadata && !this.props.imagesMetadata[attachment.thumb_url])) { if (!secureGetFromRecord(this.props.imagesMetadata, attachment.thumb_url)) {
this.handleHeightReceived(height); this.handleHeightReceived(height);
} }
}; };
@@ -111,7 +112,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
if (!attachment.image_url) { if (!attachment.image_url) {
return; return;
} }
if (!this.props.imagesMetadata || (this.props.imagesMetadata && !this.props.imagesMetadata[attachment.image_url])) { if (!secureGetFromRecord(this.props.imagesMetadata, attachment.image_url)) {
this.handleHeightReceived(height); this.handleHeightReceived(height);
} }
}; };
@@ -369,7 +370,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
<ExternalImage <ExternalImage
key={'attachment__author-icon'} key={'attachment__author-icon'}
src={attachment.author_icon} src={attachment.author_icon}
imageMetadata={this.props.imagesMetadata && this.props.imagesMetadata[attachment.author_icon]} imageMetadata={secureGetFromRecord(this.props.imagesMetadata, attachment.author_icon)}
> >
{(iconUrl) => ( {(iconUrl) => (
<img <img
@@ -459,7 +460,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
let image; let image;
if (attachment.image_url) { if (attachment.image_url) {
const imageMetadata = this.props.imagesMetadata && this.props.imagesMetadata[attachment.image_url]; const imageMetadata = secureGetFromRecord(this.props.imagesMetadata, attachment.image_url);
image = ( image = (
<div className='attachment__image-container'> <div className='attachment__image-container'>
@@ -485,7 +486,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
if (attachment.footer) { if (attachment.footer) {
let footerIcon; let footerIcon;
if (attachment.footer_icon) { if (attachment.footer_icon) {
const footerIconMetadata = this.props.imagesMetadata && this.props.imagesMetadata[attachment.footer_icon]; const footerIconMetadata = secureGetFromRecord(this.props.imagesMetadata, attachment.footer_icon);
footerIcon = ( footerIcon = (
<ExternalImage <ExternalImage
@@ -515,7 +516,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
let thumb; let thumb;
if (attachment.thumb_url) { if (attachment.thumb_url) {
const thumbMetadata = this.props.imagesMetadata && this.props.imagesMetadata[attachment.thumb_url]; const thumbMetadata = secureGetFromRecord(this.props.imagesMetadata, attachment.thumb_url);
thumb = ( thumb = (
<div className='attachment__thumb-container'> <div className='attachment__thumb-container'>

Просмотреть файл

@@ -1099,7 +1099,9 @@ export function getNeededAtMentionedUsernamesAndGroups(state: GlobalState, posts
if (attachment.fields) { if (attachment.fields) {
for (const field of attachment.fields) { for (const field of attachment.fields) {
findNeededUsernamesAndGroups(field.value); if (typeof field.value === 'string') {
findNeededUsernamesAndGroups(field.value);
}
} }
} }
} }

Просмотреть файл

@@ -26,6 +26,7 @@ import {
} from 'mattermost-redux/selectors/entities/common'; } from 'mattermost-redux/selectors/entities/common';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getDirectShowPreferences, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; import {getDirectShowPreferences, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import { import {
displayUsername, displayUsername,
filterProfilesStartingWithTerm, filterProfilesStartingWithTerm,
@@ -687,7 +688,7 @@ export function makeGetProfilesByIdsAndUsernames(): (
if (allUserIds && allUserIds.length > 0) { if (allUserIds && allUserIds.length > 0) {
const profilesById = allUserIds. const profilesById = allUserIds.
filter((userId) => allProfilesById[userId]). filter((userId) => secureGetFromRecord(allProfilesById, userId)).
map((userId) => allProfilesById[userId]); map((userId) => allProfilesById[userId]);
if (profilesById && profilesById.length > 0) { if (profilesById && profilesById.length > 0) {
@@ -697,7 +698,7 @@ export function makeGetProfilesByIdsAndUsernames(): (
if (allUsernames && allUsernames.length > 0) { if (allUsernames && allUsernames.length > 0) {
const profilesByUsername = allUsernames. const profilesByUsername = allUsernames.
filter((username) => allProfilesByUsername[username]). filter((username) => secureGetFromRecord(allProfilesByUsername, username)).
map((username) => allProfilesByUsername[username]); map((username) => allProfilesByUsername[username]);
if (profilesByUsername && profilesByUsername.length > 0) { if (profilesByUsername && profilesByUsername.length > 0) {

Просмотреть файл

@@ -249,3 +249,7 @@ export function ensureString(v: unknown) {
export function ensureNumber(v: unknown) { export function ensureNumber(v: unknown) {
return typeof v === 'number' ? v : 0; return typeof v === 'number' ? v : 0;
} }
export function secureGetFromRecord<T>(v: Record<string, T> | undefined, key: string) {
return typeof v === 'object' && v && Object.hasOwn(v, key) ? v[key] : undefined;
}

Просмотреть файл

@@ -14,7 +14,14 @@ import configureStore from 'store';
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
import {addTimeToTimestamp, TimeInformation} from 'utils/utils'; import {addTimeToTimestamp, TimeInformation} from 'utils/utils';
jest.mock('mattermost-redux/selectors/entities/users'); jest.mock('mattermost-redux/selectors/entities/users', () => {
const originalModule = jest.requireActual('mattermost-redux/selectors/entities/users');
return {
...originalModule,
getCurrentUser: jest.fn(),
getUser: jest.fn(),
};
});
jest.mock('mattermost-redux/selectors/entities/general'); jest.mock('mattermost-redux/selectors/entities/general');
jest.mock('mattermost-redux/selectors/entities/preferences'); jest.mock('mattermost-redux/selectors/entities/preferences');

Просмотреть файл

@@ -5,6 +5,7 @@ import type {MarketplaceApp, MarketplacePlugin} from '@mattermost/types/marketpl
import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {isPlugin} from 'mattermost-redux/utils/marketplace'; import {isPlugin} from 'mattermost-redux/utils/marketplace';
import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils';
import type {GlobalState} from 'types/store'; import type {GlobalState} from 'types/store';
@@ -45,6 +46,6 @@ export const getApp = (state: GlobalState, id: string): MarketplaceApp | undefin
export const getFilter = (state: GlobalState): string => state.views.marketplace.filter; export const getFilter = (state: GlobalState): string => state.views.marketplace.filter;
export const getInstalling = (state: GlobalState, id: string): boolean => Boolean(state.views.marketplace.installing[id]); export const getInstalling = (state: GlobalState, id: string): boolean => Boolean(secureGetFromRecord(state.views.marketplace.installing, id));
export const getError = (state: GlobalState, id: string): string => state.views.marketplace.errors[id]; export const getError = (state: GlobalState, id: string): string | undefined => secureGetFromRecord(state.views.marketplace.errors, id);