MM-56775 Limit Bookmarks Title and URL (#28424)

* additional validation for channelbookmark

* add fixes for webapp

* only set permissions correct for type

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Scott Bishel
2024-10-16 08:26:53 -06:00
коммит произвёл GitHub
родитель 9726eedbe2
Коммит 5dc86d5010
5 изменённых файлов: 113 добавлений и 12 удалений

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

@@ -26,10 +26,14 @@ func createBookmark(name string, bookmarkType model.ChannelBookmarkType, channel
bookmark := &model.ChannelBookmark{ bookmark := &model.ChannelBookmark{
ChannelId: channelId, ChannelId: channelId,
DisplayName: name, DisplayName: name,
LinkUrl: "https://mattermost.com",
Type: bookmarkType, Type: bookmarkType,
Emoji: ":smile:", Emoji: ":smile:",
FileId: fileId, }
if bookmarkType == model.ChannelBookmarkLink {
bookmark.LinkUrl = "https://mattermost.com"
}
if bookmarkType == model.ChannelBookmarkFile {
bookmark.FileId = fileId
} }
return bookmark return bookmark

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

@@ -5,6 +5,7 @@ package model
import ( import (
"net/http" "net/http"
"unicode/utf8"
) )
type ChannelBookmarkType string type ChannelBookmarkType string
@@ -14,6 +15,8 @@ const (
ChannelBookmarkFile ChannelBookmarkType = "file" ChannelBookmarkFile ChannelBookmarkType = "file"
BookmarkFileOwner = "bookmark" BookmarkFileOwner = "bookmark"
MaxBookmarksPerChannel = 50 MaxBookmarksPerChannel = 50
DisplayNameMaxRunes = 64
LinkMaxRunes = 1024
) )
type ChannelBookmark struct { type ChannelBookmark struct {
@@ -90,7 +93,7 @@ func (o *ChannelBookmark) IsValid() *AppError {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.owner_id.app_error", nil, "", http.StatusBadRequest) return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.owner_id.app_error", nil, "", http.StatusBadRequest)
} }
if o.DisplayName == "" { if o.DisplayName == "" || utf8.RuneCountInString(o.DisplayName) > DisplayNameMaxRunes {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
} }
@@ -98,11 +101,19 @@ func (o *ChannelBookmark) IsValid() *AppError {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if o.Type == ChannelBookmarkLink && (o.LinkUrl == "" || !IsValidHTTPURL(o.LinkUrl)) { if o.Type == ChannelBookmarkLink && o.FileId != "" {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.file_id.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.Type == ChannelBookmarkFile && o.LinkUrl != "" {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.link_url.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest) return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.link_url.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if o.Type == ChannelBookmarkLink && o.ImageUrl != "" && !IsValidHTTPURL(o.ImageUrl) { if o.Type == ChannelBookmarkLink && (o.LinkUrl == "" || !IsValidHTTPURL(o.LinkUrl) || utf8.RuneCountInString(o.LinkUrl) > LinkMaxRunes) {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.link_url.missing_or_invalid.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.Type == ChannelBookmarkLink && o.ImageUrl != "" && (!IsValidHTTPURL(o.ImageUrl) || utf8.RuneCountInString(o.ImageUrl) > LinkMaxRunes) {
return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.image_url.app_error", nil, "id="+o.Id, http.StatusBadRequest) return NewAppError("ChannelBookmark.IsValid", "model.channel_bookmark.is_valid.image_url.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }

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

@@ -4,6 +4,7 @@
package model package model
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -288,7 +289,7 @@ func TestChannelBookmarkIsValid(t *testing.T) {
false, false,
}, },
{ {
"bookmark of type link with invalid image url", "bookmark of type link with valid image url",
&ChannelBookmark{ &ChannelBookmark{
Id: NewId(), Id: NewId(),
ChannelId: NewId(), ChannelId: NewId(),
@@ -408,7 +409,7 @@ func TestChannelBookmarkIsValid(t *testing.T) {
&ChannelBookmark{ &ChannelBookmark{
Id: NewId(), Id: NewId(),
OwnerId: NewId(), OwnerId: NewId(),
ChannelId: "", ChannelId: NewId(),
FileId: NewId(), FileId: NewId(),
DisplayName: "display name", DisplayName: "display name",
SortOrder: 0, SortOrder: 0,
@@ -416,7 +417,7 @@ func TestChannelBookmarkIsValid(t *testing.T) {
ImageUrl: "", ImageUrl: "",
Emoji: "", Emoji: "",
Type: ChannelBookmarkLink, Type: ChannelBookmarkLink,
CreateAt: 0, CreateAt: 2,
UpdateAt: 3, UpdateAt: 3,
DeleteAt: 0, DeleteAt: 0,
}, },
@@ -427,7 +428,7 @@ func TestChannelBookmarkIsValid(t *testing.T) {
&ChannelBookmark{ &ChannelBookmark{
Id: NewId(), Id: NewId(),
OwnerId: NewId(), OwnerId: NewId(),
ChannelId: "", ChannelId: NewId(),
FileId: NewId(), FileId: NewId(),
DisplayName: "display name", DisplayName: "display name",
SortOrder: 0, SortOrder: 0,
@@ -435,7 +436,84 @@ func TestChannelBookmarkIsValid(t *testing.T) {
ImageUrl: "", ImageUrl: "",
Emoji: "", Emoji: "",
Type: ChannelBookmarkFile, Type: ChannelBookmarkFile,
CreateAt: 0, CreateAt: 2,
UpdateAt: 3,
DeleteAt: 0,
},
false,
},
{
"bookmark with long display name > limit",
&ChannelBookmark{
Id: NewId(),
OwnerId: NewId(),
ChannelId: NewId(),
FileId: "",
DisplayName: strings.Repeat("1", 65),
SortOrder: 0,
LinkUrl: "http://somelink",
ImageUrl: "",
Emoji: "",
Type: ChannelBookmarkLink,
CreateAt: 3,
UpdateAt: 3,
DeleteAt: 0,
},
false,
},
{
"bookmark with long display name < limit",
&ChannelBookmark{
Id: NewId(),
OwnerId: NewId(),
ChannelId: NewId(),
FileId: "",
DisplayName: strings.Repeat("1", 64),
SortOrder: 0,
LinkUrl: "http://somelink",
ImageUrl: "",
Emoji: "",
Type: ChannelBookmarkLink,
CreateAt: 3,
UpdateAt: 3,
DeleteAt: 0,
},
true,
},
{
"bookmark with link url > limit",
&ChannelBookmark{
Id: NewId(),
OwnerId: NewId(),
ChannelId: NewId(),
FileId: "",
DisplayName: "not last test",
SortOrder: 0,
LinkUrl: "http://somelink?" + strings.Repeat("h", 1024),
ImageUrl: "",
Emoji: "",
Type: ChannelBookmarkLink,
CreateAt: 3,
UpdateAt: 3,
DeleteAt: 0,
},
false,
},
{
"bookmark with image url > limit",
&ChannelBookmark{
Id: NewId(),
OwnerId: NewId(),
ChannelId: NewId(),
FileId: "",
DisplayName: "last test",
SortOrder: 0,
LinkUrl: "",
ImageUrl: "http://somelink?" + strings.Repeat("h", 1024),
Emoji: "",
Type: ChannelBookmarkLink,
CreateAt: 3,
UpdateAt: 3, UpdateAt: 3,
DeleteAt: 0, DeleteAt: 0,
}, },

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

@@ -37,6 +37,9 @@ import './bookmark_create_modal.scss';
import CreateModalNameInput from './create_modal_name_input'; import CreateModalNameInput from './create_modal_name_input';
import {useCanUploadFiles} from './utils'; import {useCanUploadFiles} from './utils';
const MAX_LINK_LENGTH = 1024;
const MAX_TITLE_LENGTH = 64;
type Props = { type Props = {
channelId: string; channelId: string;
bookmarkType?: ChannelBookmark['type']; bookmarkType?: ChannelBookmark['type'];
@@ -377,6 +380,7 @@ function ChannelBookmarkCreateModal({
{type === 'link' ? ( {type === 'link' ? (
<> <>
<Input <Input
maxLength={MAX_LINK_LENGTH}
type='text' type='text'
name='bookmark-link' name='bookmark-link'
containerClassName='linkInput' containerClassName='linkInput'
@@ -449,13 +453,14 @@ function ChannelBookmarkCreateModal({
/> />
</FieldLabel> </FieldLabel>
<CreateModalNameInput <CreateModalNameInput
maxLength={MAX_TITLE_LENGTH}
type={type} type={type}
imageUrl={icon} imageUrl={icon}
fileInfo={pendingFile || fileInfo} fileInfo={pendingFile || fileInfo}
emoji={emoji} emoji={emoji}
setEmoji={setEmoji} setEmoji={setEmoji}
displayName={displayName} displayName={displayName?.substring(0, MAX_TITLE_LENGTH)}
placeholder={displayNameValue} placeholder={displayNameValue?.substring(0, MAX_TITLE_LENGTH)}
setDisplayName={setDisplayName} setDisplayName={setDisplayName}
onAddCustomEmojiClick={onHide} onAddCustomEmojiClick={onHide}
showEmojiPicker={showEmojiPicker} showEmojiPicker={showEmojiPicker}

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

@@ -20,6 +20,7 @@ import {isKeyPressed} from 'utils/keyboard';
import BookmarkIcon from './bookmark_icon'; import BookmarkIcon from './bookmark_icon';
type Props = { type Props = {
maxLength: number;
type: ChannelBookmark['type']; type: ChannelBookmark['type'];
fileInfo: FileInfo | undefined; fileInfo: FileInfo | undefined;
imageUrl: string | undefined; imageUrl: string | undefined;
@@ -33,6 +34,7 @@ type Props = {
onAddCustomEmojiClick?: () => void; onAddCustomEmojiClick?: () => void;
} }
const CreateModalNameInput = ({ const CreateModalNameInput = ({
maxLength,
type, type,
imageUrl, imageUrl,
fileInfo, fileInfo,
@@ -138,6 +140,7 @@ const CreateModalNameInput = ({
<ChevronDownIcon size={'12px'}/> <ChevronDownIcon size={'12px'}/>
</button> </button>
<Input <Input
maxLength={maxLength}
type='text' type='text'
name='bookmark-display-name' name='bookmark-display-name'
onChange={handleInputChange} onChange={handleInputChange}