diff --git a/webapp/channels/src/components/post_view/combined_system_message/combined_system_message.tsx b/webapp/channels/src/components/post_view/combined_system_message/combined_system_message.tsx index fa72ca6d942..4beeb6bdae1 100644 --- a/webapp/channels/src/components/post_view/combined_system_message/combined_system_message.tsx +++ b/webapp/channels/src/components/post_view/combined_system_message/combined_system_message.tsx @@ -9,6 +9,7 @@ import type {UserProfile} from '@mattermost/types/users'; import {Posts} from 'mattermost-redux/constants'; import type {MessageData} from 'mattermost-redux/utils/post_list'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; import Markdown from 'components/markdown'; @@ -264,7 +265,8 @@ export class CombinedSystemMessage extends React.PureComponent { return userId !== currentUserId && userId !== currentUsername; }). map((userId) => { - return allUsernames[userId] ? `@${allUsernames[userId]}` : someone; + const username = secureGetFromRecord(allUsernames, userId); + return username ? `@${username}` : someone; }). filter((username) => { return username && username !== ''; @@ -299,11 +301,16 @@ export class CombinedSystemMessage extends React.PureComponent { singleline: true, }; + const selectedPostTypeMessage = secureGetFromRecord(postTypeMessage, postType); + if (!selectedPostTypeMessage) { + return <>; + } + if (numOthers > 1) { return ( { let localeHolder: MessageDescriptor = {}; if (numOthers === 0) { - localeHolder = postTypeMessage[postType].one; + localeHolder = selectedPostTypeMessage.one; if ( (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) { - localeHolder = postTypeMessage[postType].two; + localeHolder = selectedPostTypeMessage.two; } const formattedMessage = formatMessage(localeHolder, {firstUser, secondUser, actor}); diff --git a/webapp/channels/src/components/post_view/combined_system_message/last_users.tsx b/webapp/channels/src/components/post_view/combined_system_message/last_users.tsx index 34ac9d7d153..c866991d359 100644 --- a/webapp/channels/src/components/post_view/combined_system_message/last_users.tsx +++ b/webapp/channels/src/components/post_view/combined_system_message/last_users.tsx @@ -6,6 +6,7 @@ import {defineMessages, injectIntl} from 'react-intl'; import type {IntlShape, MessageDescriptor} from 'react-intl'; import {Posts} from 'mattermost-redux/constants'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; import Markdown from 'components/markdown'; @@ -121,10 +122,12 @@ export class LastUsers extends React.PureComponent { {numOthers: lastIndex}, ); - const actorMessage = formatMessage( - {id: typeMessage[postType].id, defaultMessage: typeMessage[postType].defaultMessage}, + const selectedTypeMessage = secureGetFromRecord(typeMessage, postType); + + const actorMessage = selectedTypeMessage ? formatMessage( + {id: selectedTypeMessage.id, defaultMessage: selectedTypeMessage.defaultMessage}, {actor}, - ); + ) : ''; return ( diff --git a/webapp/channels/src/components/post_view/message_attachments/action_button/action_button.tsx b/webapp/channels/src/components/post_view/message_attachments/action_button/action_button.tsx index 83543a69dc4..1d811c0d8f1 100644 --- a/webapp/channels/src/components/post_view/message_attachments/action_button/action_button.tsx +++ b/webapp/channels/src/components/post_view/message_attachments/action_button/action_button.tsx @@ -7,6 +7,7 @@ import styled, {css} from 'styled-components'; import type {PostAction, PostActionOption} from '@mattermost/types/integration_actions'; 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 Markdown from 'components/markdown'; @@ -51,8 +52,8 @@ const ActionButton = ({ if (action.style) { const STATUS_COLORS = getStatusColors(theme); hexColor = - STATUS_COLORS[action.style] || - theme[action.style] || + secureGetFromRecord(STATUS_COLORS, action.style) || + secureGetFromRecord(theme, action.style) || (action.style.match('^#(?:[0-9a-fA-F]{3}){1,2}$') && action.style); } diff --git a/webapp/channels/src/components/post_view/message_attachments/action_menu/index.ts b/webapp/channels/src/components/post_view/message_attachments/action_menu/index.ts index 544aa594264..3b4254956af 100644 --- a/webapp/channels/src/components/post_view/message_attachments/action_menu/index.ts +++ b/webapp/channels/src/components/post_view/message_attachments/action_menu/index.ts @@ -6,6 +6,8 @@ import type {ConnectedProps} from 'react-redux'; import type {PostAction} from '@mattermost/types/integration_actions'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; + import {autocompleteChannels} from 'actions/channel_actions'; import {autocompleteUsers} from 'actions/user_actions'; import {selectAttachmentMenuAction} from 'actions/views/posts'; @@ -22,7 +24,7 @@ export type OwnProps = { function mapStateToProps(state: GlobalState, ownProps: OwnProps) { 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 { selected, diff --git a/webapp/channels/src/components/post_view/message_attachments/message_attachment/message_attachment.tsx b/webapp/channels/src/components/post_view/message_attachments/message_attachment/message_attachment.tsx index 8f5ce5d92bd..8d7e51927a3 100644 --- a/webapp/channels/src/components/post_view/message_attachments/message_attachment/message_attachment.tsx +++ b/webapp/channels/src/components/post_view/message_attachments/message_attachment/message_attachment.tsx @@ -12,6 +12,7 @@ import type { import type {PostImage} from '@mattermost/types/posts'; import type {ActionResult} from 'mattermost-redux/types/actions'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; import {trackEvent} from 'actions/telemetry_actions'; @@ -101,7 +102,7 @@ export default class MessageAttachment extends React.PureComponent if (!attachment.thumb_url) { 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); } }; @@ -111,7 +112,7 @@ export default class MessageAttachment extends React.PureComponent if (!attachment.image_url) { 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); } }; @@ -369,7 +370,7 @@ export default class MessageAttachment extends React.PureComponent {(iconUrl) => ( let image; 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 = (
@@ -485,7 +486,7 @@ export default class MessageAttachment extends React.PureComponent if (attachment.footer) { let footerIcon; 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 = ( let thumb; 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 = (
diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts index 5b28c2877bf..88301f341ab 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/posts.ts @@ -1098,7 +1098,9 @@ export function getNeededAtMentionedUsernamesAndGroups(state: GlobalState, posts if (attachment.fields) { for (const field of attachment.fields) { - findNeededUsernamesAndGroups(field.value); + if (typeof field.value === 'string') { + findNeededUsernamesAndGroups(field.value); + } } } } diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts index a8360bcc961..1c046813d28 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts @@ -26,6 +26,7 @@ import { } from 'mattermost-redux/selectors/entities/common'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {getDirectShowPreferences, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; import { displayUsername, filterProfilesStartingWithTerm, @@ -687,7 +688,7 @@ export function makeGetProfilesByIdsAndUsernames(): ( if (allUserIds && allUserIds.length > 0) { const profilesById = allUserIds. - filter((userId) => allProfilesById[userId]). + filter((userId) => secureGetFromRecord(allProfilesById, userId)). map((userId) => allProfilesById[userId]); if (profilesById && profilesById.length > 0) { @@ -697,7 +698,7 @@ export function makeGetProfilesByIdsAndUsernames(): ( if (allUsernames && allUsernames.length > 0) { const profilesByUsername = allUsernames. - filter((username) => allProfilesByUsername[username]). + filter((username) => secureGetFromRecord(allProfilesByUsername, username)). map((username) => allProfilesByUsername[username]); if (profilesByUsername && profilesByUsername.length > 0) { diff --git a/webapp/channels/src/packages/mattermost-redux/src/utils/post_utils.ts b/webapp/channels/src/packages/mattermost-redux/src/utils/post_utils.ts index eac78172375..a4a332e323e 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/utils/post_utils.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/utils/post_utils.ts @@ -249,3 +249,7 @@ export function ensureString(v: unknown) { export function ensureNumber(v: unknown) { return typeof v === 'number' ? v : 0; } + +export function secureGetFromRecord(v: Record | undefined, key: string) { + return typeof v === 'object' && v && Object.hasOwn(v, key) ? v[key] : undefined; +} diff --git a/webapp/channels/src/selectors/views/custom_status.test.ts b/webapp/channels/src/selectors/views/custom_status.test.ts index 9ccd0c72177..d2bd6a63c56 100644 --- a/webapp/channels/src/selectors/views/custom_status.test.ts +++ b/webapp/channels/src/selectors/views/custom_status.test.ts @@ -14,7 +14,14 @@ import configureStore from 'store'; import {TestHelper} from 'utils/test_helper'; 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/preferences'); diff --git a/webapp/channels/src/selectors/views/marketplace.ts b/webapp/channels/src/selectors/views/marketplace.ts index ea9e3a81530..3f23fa6a062 100644 --- a/webapp/channels/src/selectors/views/marketplace.ts +++ b/webapp/channels/src/selectors/views/marketplace.ts @@ -5,6 +5,7 @@ import type {MarketplaceApp, MarketplacePlugin} from '@mattermost/types/marketpl import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {isPlugin} from 'mattermost-redux/utils/marketplace'; +import {secureGetFromRecord} from 'mattermost-redux/utils/post_utils'; 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 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);