mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-18 18:18:23 -05:00
Content flagging file downloads (#34480)
* Server change donw * webapp changes * Disabled file actions * lint fixes * Removed leftover comment * CI * Added tests * lint fixes --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
59b3b5797d
commit
e8406345a5
18 changed files with 227 additions and 48 deletions
|
|
@ -630,7 +630,7 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
isContentReviewer := false
|
||||
asContentReviewer, _ := strconv.ParseBool(r.URL.Query().Get("as_content_reviewer"))
|
||||
asContentReviewer, _ := strconv.ParseBool(r.URL.Query().Get(model.AsContentReviewerParam))
|
||||
if asContentReviewer {
|
||||
requireContentFlaggingEnabled(c)
|
||||
if c.Err != nil {
|
||||
|
|
|
|||
|
|
@ -513,31 +513,77 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterToAuditRec(auditRec, "force_download", forceDownload)
|
||||
|
||||
info, err := c.App.GetFileInfo(c.AppContext, c.Params.FileId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
setInaccessibleFileHeader(w, err)
|
||||
fileInfos, storeErr := c.App.Srv().Store().FileInfo().GetByIds([]string{c.Params.FileId}, true, true)
|
||||
if storeErr != nil {
|
||||
c.Err = model.NewAppError("getFile", "api.file.get_file_info.app_error", nil, "", http.StatusInternalServerError)
|
||||
setInaccessibleFileHeader(w, c.Err)
|
||||
return
|
||||
} else if len(fileInfos) == 0 {
|
||||
c.Err = model.NewAppError("getFile", "api.file.get_file_info.app_error", nil, "", http.StatusNotFound)
|
||||
setInaccessibleFileHeader(w, c.Err)
|
||||
return
|
||||
}
|
||||
model.AddEventParameterAuditableToAuditRec(auditRec, "file", info)
|
||||
|
||||
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
|
||||
fileInfo := fileInfos[0]
|
||||
|
||||
channel, err := c.App.GetChannel(c.AppContext, fileInfo.ChannelId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
|
||||
if info.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
|
||||
isContentReviewer := false
|
||||
asContentReviewer, _ := strconv.ParseBool(r.URL.Query().Get(model.AsContentReviewerParam))
|
||||
if asContentReviewer {
|
||||
requireContentFlaggingEnabled(c)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
flaggedPostId := r.URL.Query().Get("flagged_post_id")
|
||||
requireFlaggedPost(c, flaggedPostId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if flaggedPostId != fileInfo.PostId {
|
||||
c.Err = model.NewAppError("getFile", "api.file.get_file.invalid_flagged_post.app_error", nil, "file_id="+fileInfo.Id+", flagged_post_id="+flaggedPostId, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
requireTeamContentReviewer(c, c.AppContext.Session().UserId, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
isContentReviewer = true
|
||||
}
|
||||
|
||||
// at this point we may have fetched a deleted file info and
|
||||
// if the user is not a content reviewer, the request should fail as
|
||||
// fetching deleted file info is only allowed for content reviewers of the specific post
|
||||
if fileInfo.DeleteAt != 0 && !isContentReviewer {
|
||||
c.Err = model.NewAppError("getFile", "app.file_info.get.app_error", nil, "", http.StatusNotFound)
|
||||
setInaccessibleFileHeader(w, c.Err)
|
||||
return
|
||||
}
|
||||
|
||||
model.AddEventParameterAuditableToAuditRec(auditRec, "file", fileInfo)
|
||||
|
||||
if !isContentReviewer {
|
||||
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
|
||||
if fileInfo.CreatorId == model.BookmarkFileOwner {
|
||||
if !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if fileInfo.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
} else if info.CreatorId != c.AppContext.Session().UserId && !perm {
|
||||
c.SetPermissionError(model.PermissionReadChannelContent)
|
||||
return
|
||||
}
|
||||
|
||||
fileReader, err := c.App.FileReader(info.Path)
|
||||
fileReader, err := c.App.FileReader(fileInfo.Path)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusNotFound
|
||||
|
|
@ -547,7 +593,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
auditRec.Success()
|
||||
|
||||
web.WriteFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r)
|
||||
web.WriteFileResponse(fileInfo.Name, fileInfo.MimeType, fileInfo.Size, time.Unix(0, fileInfo.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r)
|
||||
}
|
||||
|
||||
func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -790,35 +790,112 @@ func TestGetFile(t *testing.T) {
|
|||
t.Skip("skipping because no file driver is enabled")
|
||||
}
|
||||
|
||||
sent, err := testutils.ReadTestFile("test.png")
|
||||
require.NoError(t, err)
|
||||
t.Run("base case", func(t *testing.T) {
|
||||
sent, err := testutils.ReadTestFile("test.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
fileResp, _, err := client.UploadFile(context.Background(), sent, channel.Id, "test.png")
|
||||
require.NoError(t, err)
|
||||
fileResp, _, err := client.UploadFile(context.Background(), sent, channel.Id, "test.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
fileId := fileResp.FileInfos[0].Id
|
||||
fileId := fileResp.FileInfos[0].Id
|
||||
|
||||
data, _, err := client.GetFile(context.Background(), fileId)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(data), "should not be empty")
|
||||
data, _, err := client.GetFile(context.Background(), fileId)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(data), "should not be empty")
|
||||
|
||||
for i := range data {
|
||||
require.Equal(t, sent[i], data[i], "received file didn't match sent one")
|
||||
}
|
||||
for i := range data {
|
||||
require.Equal(t, sent[i], data[i], "received file didn't match sent one")
|
||||
}
|
||||
|
||||
_, resp, err := client.GetFile(context.Background(), "junk")
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
_, resp, err := client.GetFile(context.Background(), "junk")
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp, err = client.GetFile(context.Background(), model.NewId())
|
||||
require.Error(t, err)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
_, resp, err = client.GetFile(context.Background(), model.NewId())
|
||||
require.Error(t, err)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
_, err = client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
_, resp, err = client.GetFile(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
_, err = client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
_, resp, err = client.GetFile(context.Background(), fileId)
|
||||
require.Error(t, err)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("content reviewer should be able to get file of channel and team they are not a member of", func(t *testing.T) {
|
||||
th.LoginBasic(t)
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "failed to set license")
|
||||
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
|
||||
newChannel := th.CreatePrivateChannel(t)
|
||||
|
||||
sent, err := testutils.ReadTestFile("test.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
fileResp, _, err := client.UploadFile(context.Background(), sent, channel.Id, "test.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
post := th.CreatePostWithFilesWithClient(t, client, newChannel, fileResp.FileInfos[0])
|
||||
|
||||
reviewer := th.CreateUser(t)
|
||||
response, err := th.SystemAdminClient.SaveContentFlaggingSettings(context.Background(), &model.ContentFlaggingSettingsRequest{
|
||||
ContentFlaggingSettingsBase: model.ContentFlaggingSettingsBase{
|
||||
EnableContentFlagging: model.NewPointer(true),
|
||||
},
|
||||
ReviewerSettings: &model.ReviewSettingsRequest{
|
||||
ReviewerSettings: model.ReviewerSettings{
|
||||
CommonReviewers: model.NewPointer(true),
|
||||
},
|
||||
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
||||
CommonReviewerIds: []string{reviewer.Id},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
|
||||
response, err = client.FlagPostForContentReview(context.Background(), post.Id, &model.FlagContentRequest{
|
||||
Reason: "Sensitive data",
|
||||
Comment: "This is sensitive content",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
|
||||
reviewerClient := th.CreateClient()
|
||||
_, response, err = reviewerClient.Login(context.Background(), reviewer.Email, "Pa$$word11")
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
|
||||
_, response, err = reviewerClient.GetFileAsContentReviewer(context.Background(), fileResp.FileInfos[0].Id, post.Id)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
|
||||
// Try again after removing the user from content reviewers
|
||||
response, err = th.SystemAdminClient.SaveContentFlaggingSettings(context.Background(), &model.ContentFlaggingSettingsRequest{
|
||||
ContentFlaggingSettingsBase: model.ContentFlaggingSettingsBase{
|
||||
EnableContentFlagging: model.NewPointer(true),
|
||||
},
|
||||
ReviewerSettings: &model.ReviewSettingsRequest{
|
||||
ReviewerSettings: model.ReviewerSettings{
|
||||
CommonReviewers: model.NewPointer(true),
|
||||
},
|
||||
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
||||
CommonReviewerIds: []string{th.BasicUser.Id},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
|
||||
_, response, err = reviewerClient.GetFileAsContentReviewer(context.Background(), fileResp.FileInfos[0].Id, post.Id)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, response)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFileAsSystemAdmin(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func getTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
isContentReviewer := false
|
||||
asContentReviewer, _ := strconv.ParseBool(r.URL.Query().Get("as_content_reviewer"))
|
||||
asContentReviewer, _ := strconv.ParseBool(r.URL.Query().Get(model.AsContentReviewerParam))
|
||||
if asContentReviewer {
|
||||
requireContentFlaggingEnabled(c)
|
||||
if c.Err != nil {
|
||||
|
|
|
|||
|
|
@ -8536,11 +8536,11 @@ func (s *RetryLayerPostStore) RefreshPostStats() error {
|
|||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) RestoreContentFlaggedPost(post *model.Post, deletedBy string, statusFieldId string) error {
|
||||
func (s *RetryLayerPostStore) RestoreContentFlaggedPost(post *model.Post, statusFieldId string, contentFlaggingManagedFieldId string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.PostStore.RestoreContentFlaggedPost(post, deletedBy, statusFieldId)
|
||||
err := s.PostStore.RestoreContentFlaggedPost(post, statusFieldId, contentFlaggingManagedFieldId)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6814,10 +6814,10 @@ func (s *TimerLayerPostStore) RefreshPostStats() error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) RestoreContentFlaggedPost(post *model.Post, deletedBy string, statusFieldId string) error {
|
||||
func (s *TimerLayerPostStore) RestoreContentFlaggedPost(post *model.Post, statusFieldId string, contentFlaggingManagedFieldId string) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.PostStore.RestoreContentFlaggedPost(post, deletedBy, statusFieldId)
|
||||
err := s.PostStore.RestoreContentFlaggedPost(post, statusFieldId, contentFlaggingManagedFieldId)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
|
|
|||
|
|
@ -2152,10 +2152,18 @@
|
|||
"id": "api.file.file_size.app_error",
|
||||
"translation": "Unable to get the file size."
|
||||
},
|
||||
{
|
||||
"id": "api.file.get_file.invalid_flagged_post.app_error",
|
||||
"translation": "Mismatched flagged post ID specified."
|
||||
},
|
||||
{
|
||||
"id": "api.file.get_file.public_invalid.app_error",
|
||||
"translation": "The public link does not appear to be valid."
|
||||
},
|
||||
{
|
||||
"id": "api.file.get_file_info.app_error",
|
||||
"translation": "Failed to get file info."
|
||||
},
|
||||
{
|
||||
"id": "api.file.get_file_preview.no_preview.app_error",
|
||||
"translation": "File doesn't have a preview image."
|
||||
|
|
|
|||
|
|
@ -1960,7 +1960,7 @@ func (c *Client4) GetTeam(ctx context.Context, teamId, etag string) (*Team, *Res
|
|||
// GetTeamAsContentReviewer returns a team based on the provided team id string, fetching it as a Content Reviewer for a flagged post.
|
||||
func (c *Client4) GetTeamAsContentReviewer(ctx context.Context, teamId, etag, flaggedPostId string) (*Team, *Response, error) {
|
||||
values := url.Values{}
|
||||
values.Set("as_content_reviewer", c.boolString(true))
|
||||
values.Set(AsContentReviewerParam, c.boolString(true))
|
||||
values.Set("flagged_post_id", flaggedPostId)
|
||||
|
||||
route := c.teamRoute(teamId) + "?" + values.Encode()
|
||||
|
|
@ -2644,7 +2644,7 @@ func (c *Client4) GetChannel(ctx context.Context, channelId, etag string) (*Chan
|
|||
// GetChannelAsContentReviewer returns a channel based on the provided channel id string, fetching it as a Content Reviewer for a flagged post.
|
||||
func (c *Client4) GetChannelAsContentReviewer(ctx context.Context, channelId, etag, flaggedPostId string) (*Channel, *Response, error) {
|
||||
values := url.Values{}
|
||||
values.Set("as_content_reviewer", c.boolString(true))
|
||||
values.Set(AsContentReviewerParam, c.boolString(true))
|
||||
values.Set("flagged_post_id", flaggedPostId)
|
||||
|
||||
route := c.channelRoute(channelId) + "?" + values.Encode()
|
||||
|
|
@ -3800,6 +3800,19 @@ func (c *Client4) GetFile(ctx context.Context, fileId string) ([]byte, *Response
|
|||
return ReadBytesFromResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) GetFileAsContentReviewer(ctx context.Context, fileId, flaggedPostId string) ([]byte, *Response, error) {
|
||||
values := url.Values{}
|
||||
values.Set(AsContentReviewerParam, c.boolString(true))
|
||||
values.Set("flagged_post_id", flaggedPostId)
|
||||
|
||||
r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+"?"+values.Encode(), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return ReadBytesFromResponse(r)
|
||||
}
|
||||
|
||||
// DownloadFile gets the bytes for a file by id, optionally adding headers to force the browser to download it.
|
||||
func (c *Client4) DownloadFile(ctx context.Context, fileId string, download bool) ([]byte, *Response, error) {
|
||||
values := url.Values{}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ const (
|
|||
ContentFlaggingBotUsername = "content-review"
|
||||
|
||||
commentMaxRunes = 1000
|
||||
|
||||
AsContentReviewerParam = "as_content_reviewer"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ type Props = PropsFromRedux & {
|
|||
handleFileDropdownOpened?: (open: boolean) => void;
|
||||
disableThumbnail?: boolean;
|
||||
disableActions?: boolean;
|
||||
overrideGenerateFileDownloadUrl?: (fileId: string) => string;
|
||||
};
|
||||
|
||||
export default function FileAttachment(props: Props) {
|
||||
|
|
@ -345,6 +346,7 @@ export default function FileAttachment(props: Props) {
|
|||
canDownload={props.canDownloadFiles}
|
||||
handleImageClick={onAttachmentClick}
|
||||
iconClass={'post-image__download'}
|
||||
overrideGenerateFileDownloadUrl={props.overrideGenerateFileDownloadUrl}
|
||||
>
|
||||
<i className='icon icon-download-outline'/>
|
||||
</FilenameOverlay>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ type Props = {
|
|||
* Optional class like for icon
|
||||
*/
|
||||
iconClass?: string;
|
||||
|
||||
overrideGenerateFileDownloadUrl?: (fileId: string) => string;
|
||||
}
|
||||
|
||||
export default class FilenameOverlay extends React.PureComponent<Props> {
|
||||
|
|
@ -57,6 +59,7 @@ export default class FilenameOverlay extends React.PureComponent<Props> {
|
|||
fileInfo,
|
||||
handleImageClick,
|
||||
iconClass,
|
||||
overrideGenerateFileDownloadUrl,
|
||||
} = this.props;
|
||||
|
||||
const fileName = fileInfo.name;
|
||||
|
|
@ -86,7 +89,7 @@ export default class FilenameOverlay extends React.PureComponent<Props> {
|
|||
title={defineMessage({id: 'view_image_popover.download', defaultMessage: 'Download'})}
|
||||
>
|
||||
<ExternalLink
|
||||
href={getFileDownloadUrl(fileInfo.id)}
|
||||
href={(overrideGenerateFileDownloadUrl || getFileDownloadUrl)(fileInfo.id)}
|
||||
aria-label={localizeMessage({id: 'view_image_popover.download', defaultMessage: 'Download'}).toLowerCase()}
|
||||
className='btn btn-icon btn-sm'
|
||||
download={fileName}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export default function FileAttachmentList(props: Props) {
|
|||
disableActions={props.disableActions}
|
||||
disableThumbnail={isDeleted}
|
||||
disablePreview={isDeleted}
|
||||
overrideGenerateFileDownloadUrl={props.overrideGenerateFileDownloadUrl}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export type OwnProps = {
|
|||
disableDownload?: boolean;
|
||||
disableActions?: boolean;
|
||||
usePostAsSource?: boolean;
|
||||
overrideGenerateFileDownloadUrl?: (fileId: string) => string;
|
||||
}
|
||||
|
||||
function makeMapStateToProps() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type {Post} from '@mattermost/types/posts';
|
|||
import type {NameMappedPropertyFields, PropertyValue} from '@mattermost/types/properties';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {getFileDownloadUrl} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import AtMention from 'components/at_mention';
|
||||
import {useContentFlaggingFields, usePostContentFlaggingValues} from 'components/common/hooks/useContentFlaggingFields';
|
||||
|
|
@ -144,6 +145,7 @@ export function DataSpillageReport({post, isRHS}: Props) {
|
|||
fetchDeletedPost: true,
|
||||
getChannel: getChannel(reportedPostId),
|
||||
getTeam: getTeam(reportedPostId),
|
||||
generateFileDownloadUrl: generateFileDownloadUrl(reportedPostId),
|
||||
},
|
||||
reporting_comment: {
|
||||
placeholder: formatMessage({id: 'data_spillage_report_post.reporting_comment.placeholder', defaultMessage: 'No comment'}),
|
||||
|
|
@ -244,3 +246,7 @@ function getTeam(flaggedPostId: string) {
|
|||
return Client4.getTeam(teamId, true, flaggedPostId);
|
||||
};
|
||||
}
|
||||
|
||||
function generateFileDownloadUrl(flaggedPostId: string) {
|
||||
return (fileId: string) => getFileDownloadUrl(fileId, true, flaggedPostId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,15 @@ export type Props = OwnProps & {
|
|||
compactDisplay: boolean;
|
||||
isPostPriorityEnabled: boolean;
|
||||
handleFileDropdownOpened?: (open: boolean) => void;
|
||||
overrideGenerateFileDownloadUrl?: (fileId: string) => string;
|
||||
disableActions?: boolean;
|
||||
actions: {
|
||||
toggleEmbedVisibility: (id: string) => void;
|
||||
};
|
||||
};
|
||||
|
||||
const PostMessagePreview = (props: Props) => {
|
||||
const {currentTeamUrl, channelDisplayName, user, previewPost, metadata, isEmbedVisible, compactDisplay, preventClickAction, previewFooterMessage, handleFileDropdownOpened, isPostPriorityEnabled} = props;
|
||||
const {currentTeamUrl, channelDisplayName, user, previewPost, metadata, isEmbedVisible, compactDisplay, preventClickAction, previewFooterMessage, handleFileDropdownOpened, isPostPriorityEnabled, overrideGenerateFileDownloadUrl, disableActions} = props;
|
||||
|
||||
const toggleEmbedVisibility = () => {
|
||||
if (previewPost) {
|
||||
|
|
@ -63,6 +65,8 @@ const PostMessagePreview = (props: Props) => {
|
|||
isInPermalink={true}
|
||||
handleFileDropdownOpened={handleFileDropdownOpened}
|
||||
usePostAsSource={props.usePostAsSource}
|
||||
overrideGenerateFileDownloadUrl={overrideGenerateFileDownloadUrl}
|
||||
disableActions={disableActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export type PostPreviewFieldMetadata = {
|
|||
fetchDeletedPost?: boolean;
|
||||
getChannel?: (channelId: string) => Promise<Channel>;
|
||||
getTeam?: (teamId: string) => Promise<Team>;
|
||||
generateFileDownloadUrl?: (fileId: string) => string;
|
||||
};
|
||||
|
||||
export type UserPropertyMetadata = {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ export default function PostPreviewPropertyRenderer({value, metadata}: Props) {
|
|||
preventClickAction={true}
|
||||
previewFooterMessage={postPreviewFooterMessage}
|
||||
usePostAsSource={true}
|
||||
overrideGenerateFileDownloadUrl={metadata?.generateFileDownloadUrl}
|
||||
disableActions={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {buildQueryString} from '@mattermost/client/lib/helpers';
|
||||
import type {FileInfo} from '@mattermost/types/files';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
|
@ -55,8 +56,20 @@ export function getFileUrl(fileId: string): string {
|
|||
return Client4.getFileRoute(fileId);
|
||||
}
|
||||
|
||||
export function getFileDownloadUrl(fileId: string): string {
|
||||
return `${Client4.getFileRoute(fileId)}?download=1`;
|
||||
export function getFileDownloadUrl(fileId: string, asContentReviewer?: boolean, flaggedPostId?: string): string {
|
||||
const queryParamsArgs: Record<string, any> = {};
|
||||
queryParamsArgs.download = 1;
|
||||
|
||||
if (asContentReviewer) {
|
||||
queryParamsArgs.as_content_reviewer = true;
|
||||
}
|
||||
|
||||
if (flaggedPostId) {
|
||||
queryParamsArgs.flagged_post_id = flaggedPostId;
|
||||
}
|
||||
|
||||
const queryParams = buildQueryString(queryParamsArgs);
|
||||
return `${Client4.getFileRoute(fileId)}${queryParams}`;
|
||||
}
|
||||
|
||||
export function getFileThumbnailUrl(fileId: string): string {
|
||||
|
|
|
|||
Loading…
Reference in a new issue