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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9726eedbe2
Коммит
5dc86d5010
@@ -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}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user