diff options
Diffstat (limited to 'server/helpers/custom-validators/activitypub')
4 files changed, 124 insertions, 146 deletions
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts new file mode 100644 index 000000000..dd671c4cf --- /dev/null +++ b/server/helpers/custom-validators/activitypub/activity.ts | |||
@@ -0,0 +1,34 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import { | ||
3 | isVideoChannelCreateActivityValid, | ||
4 | isVideoTorrentAddActivityValid, | ||
5 | isVideoTorrentUpdateActivityValid, | ||
6 | isVideoChannelUpdateActivityValid | ||
7 | } from './videos' | ||
8 | |||
9 | function isRootActivityValid (activity: any) { | ||
10 | return Array.isArray(activity['@context']) && | ||
11 | ( | ||
12 | (activity.type === 'Collection' || activity.type === 'OrderedCollection') && | ||
13 | validator.isInt(activity.totalItems, { min: 0 }) && | ||
14 | Array.isArray(activity.items) | ||
15 | ) || | ||
16 | ( | ||
17 | validator.isURL(activity.id) && | ||
18 | validator.isURL(activity.actor) | ||
19 | ) | ||
20 | } | ||
21 | |||
22 | function isActivityValid (activity: any) { | ||
23 | return isVideoTorrentAddActivityValid(activity) || | ||
24 | isVideoChannelCreateActivityValid(activity) || | ||
25 | isVideoTorrentUpdateActivityValid(activity) || | ||
26 | isVideoChannelUpdateActivityValid(activity) | ||
27 | } | ||
28 | |||
29 | // --------------------------------------------------------------------------- | ||
30 | |||
31 | export { | ||
32 | isRootActivityValid, | ||
33 | isActivityValid | ||
34 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts index 800f0ddf3..0eba06a7b 100644 --- a/server/helpers/custom-validators/activitypub/index.ts +++ b/server/helpers/custom-validators/activitypub/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './account' | 1 | export * from './account' |
2 | export * from './activity' | ||
2 | export * from './signature' | 3 | export * from './signature' |
3 | export * from './misc' | 4 | export * from './misc' |
4 | export * from './videos' | 5 | export * from './videos' |
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index 806d33483..f049f5a8c 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts | |||
@@ -12,6 +12,16 @@ function isActivityPubUrlValid (url: string) { | |||
12 | return exists(url) && validator.isURL(url, isURLOptions) | 12 | return exists(url) && validator.isURL(url, isURLOptions) |
13 | } | 13 | } |
14 | 14 | ||
15 | function isBaseActivityValid (activity: any, type: string) { | ||
16 | return Array.isArray(activity['@context']) && | ||
17 | activity.type === type && | ||
18 | validator.isURL(activity.id) && | ||
19 | validator.isURL(activity.actor) && | ||
20 | Array.isArray(activity.to) && | ||
21 | activity.to.every(t => validator.isURL(t)) | ||
22 | } | ||
23 | |||
15 | export { | 24 | export { |
16 | isActivityPubUrlValid | 25 | isActivityPubUrlValid, |
26 | isBaseActivityValid | ||
17 | } | 27 | } |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index e0ffba679..9233a1359 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -1,184 +1,117 @@ | |||
1 | import 'express-validator' | 1 | import * as validator from 'validator' |
2 | import { has, values } from 'lodash' | ||
3 | 2 | ||
4 | import { | 3 | import { |
5 | REQUEST_ENDPOINTS, | 4 | ACTIVITY_PUB |
6 | REQUEST_ENDPOINT_ACTIONS, | ||
7 | REQUEST_VIDEO_EVENT_TYPES | ||
8 | } from '../../../initializers' | 5 | } from '../../../initializers' |
9 | import { isArray, isDateValid, isUUIDValid } from '../misc' | 6 | import { isDateValid, isUUIDValid } from '../misc' |
10 | import { | 7 | import { |
11 | isVideoThumbnailDataValid, | ||
12 | isVideoAbuseReasonValid, | ||
13 | isVideoAbuseReporterUsernameValid, | ||
14 | isVideoViewsValid, | 8 | isVideoViewsValid, |
15 | isVideoLikesValid, | ||
16 | isVideoDislikesValid, | ||
17 | isVideoEventCountValid, | ||
18 | isRemoteVideoCategoryValid, | ||
19 | isRemoteVideoLicenceValid, | ||
20 | isRemoteVideoLanguageValid, | ||
21 | isVideoNSFWValid, | 9 | isVideoNSFWValid, |
22 | isVideoTruncatedDescriptionValid, | 10 | isVideoTruncatedDescriptionValid, |
23 | isVideoDurationValid, | 11 | isVideoDurationValid, |
24 | isVideoFileInfoHashValid, | ||
25 | isVideoNameValid, | 12 | isVideoNameValid, |
26 | isVideoTagsValid, | 13 | isVideoTagValid |
27 | isVideoFileExtnameValid, | ||
28 | isVideoFileResolutionValid | ||
29 | } from '../videos' | 14 | } from '../videos' |
30 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' | 15 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' |
31 | import { isVideoAuthorNameValid } from '../video-authors' | 16 | import { isBaseActivityValid } from './misc' |
32 | 17 | ||
33 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] | 18 | function isVideoTorrentAddActivityValid (activity: any) { |
34 | 19 | return isBaseActivityValid(activity, 'Add') && | |
35 | const checkers: { [ id: string ]: (obj: any) => boolean } = {} | 20 | isVideoTorrentObjectValid(activity.object) |
36 | checkers[ENDPOINT_ACTIONS.ADD_VIDEO] = checkAddVideo | 21 | } |
37 | checkers[ENDPOINT_ACTIONS.UPDATE_VIDEO] = checkUpdateVideo | 22 | |
38 | checkers[ENDPOINT_ACTIONS.REMOVE_VIDEO] = checkRemoveVideo | 23 | function isVideoTorrentUpdateActivityValid (activity: any) { |
39 | checkers[ENDPOINT_ACTIONS.REPORT_ABUSE] = checkReportVideo | 24 | return isBaseActivityValid(activity, 'Update') && |
40 | checkers[ENDPOINT_ACTIONS.ADD_CHANNEL] = checkAddVideoChannel | 25 | isVideoTorrentObjectValid(activity.object) |
41 | checkers[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = checkUpdateVideoChannel | ||
42 | checkers[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = checkRemoveVideoChannel | ||
43 | checkers[ENDPOINT_ACTIONS.ADD_AUTHOR] = checkAddAuthor | ||
44 | checkers[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = checkRemoveAuthor | ||
45 | |||
46 | function removeBadRequestVideos (requests: any[]) { | ||
47 | for (let i = requests.length - 1; i >= 0 ; i--) { | ||
48 | const request = requests[i] | ||
49 | const video = request.data | ||
50 | |||
51 | if ( | ||
52 | !video || | ||
53 | checkers[request.type] === undefined || | ||
54 | checkers[request.type](video) === false | ||
55 | ) { | ||
56 | requests.splice(i, 1) | ||
57 | } | ||
58 | } | ||
59 | } | 26 | } |
60 | 27 | ||
61 | function removeBadRequestVideosQadu (requests: any[]) { | 28 | function isVideoTorrentObjectValid (video: any) { |
62 | for (let i = requests.length - 1; i >= 0 ; i--) { | 29 | return video.type === 'Video' && |
63 | const request = requests[i] | 30 | isVideoNameValid(video.name) && |
64 | const video = request.data | 31 | isVideoDurationValid(video.duration) && |
65 | 32 | isUUIDValid(video.uuid) && | |
66 | if ( | 33 | setValidRemoteTags(video) && |
67 | !video || | 34 | isRemoteIdentifierValid(video.category) && |
68 | ( | 35 | isRemoteIdentifierValid(video.licence) && |
69 | isUUIDValid(video.uuid) && | 36 | isRemoteIdentifierValid(video.language) && |
70 | (has(video, 'views') === false || isVideoViewsValid(video.views)) && | 37 | isVideoViewsValid(video.video) && |
71 | (has(video, 'likes') === false || isVideoLikesValid(video.likes)) && | 38 | isVideoNSFWValid(video.nsfw) && |
72 | (has(video, 'dislikes') === false || isVideoDislikesValid(video.dislikes)) | 39 | isDateValid(video.published) && |
73 | ) === false | 40 | isDateValid(video.updated) && |
74 | ) { | 41 | isRemoteVideoContentValid(video.mediaType, video.content) && |
75 | requests.splice(i, 1) | 42 | isRemoteVideoIconValid(video.icon) && |
76 | } | 43 | setValidRemoteVideoUrls(video.url) |
77 | } | ||
78 | } | 44 | } |
79 | 45 | ||
80 | function removeBadRequestVideosEvents (requests: any[]) { | 46 | function isVideoChannelCreateActivityValid (activity: any) { |
81 | for (let i = requests.length - 1; i >= 0 ; i--) { | 47 | return isBaseActivityValid(activity, 'Create') && |
82 | const request = requests[i] | 48 | isVideoChannelObjectValid(activity.object) |
83 | const eventData = request.data | 49 | } |
84 | 50 | ||
85 | if ( | 51 | function isVideoChannelUpdateActivityValid (activity: any) { |
86 | !eventData || | 52 | return isBaseActivityValid(activity, 'Update') && |
87 | ( | 53 | isVideoChannelObjectValid(activity.object) |
88 | isUUIDValid(eventData.uuid) && | 54 | } |
89 | values(REQUEST_VIDEO_EVENT_TYPES).indexOf(eventData.eventType) !== -1 && | 55 | |
90 | isVideoEventCountValid(eventData.count) | 56 | function isVideoChannelObjectValid (videoChannel: any) { |
91 | ) === false | 57 | return videoChannel.type === 'VideoChannel' && |
92 | ) { | 58 | isVideoChannelNameValid(videoChannel.name) && |
93 | requests.splice(i, 1) | 59 | isVideoChannelDescriptionValid(videoChannel.description) && |
94 | } | 60 | isUUIDValid(videoChannel.uuid) |
95 | } | ||
96 | } | 61 | } |
97 | 62 | ||
98 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
99 | 64 | ||
100 | export { | 65 | export { |
101 | removeBadRequestVideos, | 66 | isVideoTorrentAddActivityValid, |
102 | removeBadRequestVideosQadu, | 67 | isVideoChannelCreateActivityValid, |
103 | removeBadRequestVideosEvents | 68 | isVideoTorrentUpdateActivityValid, |
69 | isVideoChannelUpdateActivityValid | ||
104 | } | 70 | } |
105 | 71 | ||
106 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
107 | 73 | ||
108 | function isCommonVideoAttributesValid (video: any) { | 74 | function setValidRemoteTags (video: any) { |
109 | return isDateValid(video.createdAt) && | 75 | if (Array.isArray(video.tag) === false) return false |
110 | isDateValid(video.updatedAt) && | ||
111 | isRemoteVideoCategoryValid(video.category) && | ||
112 | isRemoteVideoLicenceValid(video.licence) && | ||
113 | isRemoteVideoLanguageValid(video.language) && | ||
114 | isVideoNSFWValid(video.nsfw) && | ||
115 | isVideoTruncatedDescriptionValid(video.truncatedDescription) && | ||
116 | isVideoDurationValid(video.duration) && | ||
117 | isVideoNameValid(video.name) && | ||
118 | isVideoTagsValid(video.tags) && | ||
119 | isUUIDValid(video.uuid) && | ||
120 | isVideoViewsValid(video.views) && | ||
121 | isVideoLikesValid(video.likes) && | ||
122 | isVideoDislikesValid(video.dislikes) && | ||
123 | isArray(video.files) && | ||
124 | video.files.every(videoFile => { | ||
125 | if (!videoFile) return false | ||
126 | |||
127 | return ( | ||
128 | isVideoFileInfoHashValid(videoFile.infoHash) && | ||
129 | isVideoFileExtnameValid(videoFile.extname) && | ||
130 | isVideoFileResolutionValid(videoFile.resolution) | ||
131 | ) | ||
132 | }) | ||
133 | } | ||
134 | 76 | ||
135 | function checkAddVideo (video: any) { | 77 | const newTag = video.tag.filter(t => { |
136 | return isCommonVideoAttributesValid(video) && | 78 | return t.type === 'Hashtag' && |
137 | isUUIDValid(video.channelUUID) && | 79 | isVideoTagValid(t.name) |
138 | isVideoThumbnailDataValid(video.thumbnailData) | 80 | }) |
139 | } | ||
140 | 81 | ||
141 | function checkUpdateVideo (video: any) { | 82 | video.tag = newTag |
142 | return isCommonVideoAttributesValid(video) | 83 | return true |
143 | } | 84 | } |
144 | 85 | ||
145 | function checkRemoveVideo (video: any) { | 86 | function isRemoteIdentifierValid (data: any) { |
146 | return isUUIDValid(video.uuid) | 87 | return validator.isInt(data.identifier, { min: 0 }) |
147 | } | 88 | } |
148 | 89 | ||
149 | function checkReportVideo (abuse: any) { | 90 | function isRemoteVideoContentValid (mediaType: string, content: string) { |
150 | return isUUIDValid(abuse.videoUUID) && | 91 | return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) |
151 | isVideoAbuseReasonValid(abuse.reportReason) && | ||
152 | isVideoAbuseReporterUsernameValid(abuse.reporterUsername) | ||
153 | } | 92 | } |
154 | 93 | ||
155 | function checkAddVideoChannel (videoChannel: any) { | 94 | function isRemoteVideoIconValid (icon: any) { |
156 | return isUUIDValid(videoChannel.uuid) && | 95 | return icon.type === 'Image' && |
157 | isVideoChannelNameValid(videoChannel.name) && | 96 | validator.isURL(icon.url) && |
158 | isVideoChannelDescriptionValid(videoChannel.description) && | 97 | icon.mediaType === 'image/jpeg' && |
159 | isDateValid(videoChannel.createdAt) && | 98 | validator.isInt(icon.width, { min: 0 }) && |
160 | isDateValid(videoChannel.updatedAt) && | 99 | validator.isInt(icon.height, { min: 0 }) |
161 | isUUIDValid(videoChannel.ownerUUID) | ||
162 | } | 100 | } |
163 | 101 | ||
164 | function checkUpdateVideoChannel (videoChannel: any) { | 102 | function setValidRemoteVideoUrls (video: any) { |
165 | return isUUIDValid(videoChannel.uuid) && | 103 | if (Array.isArray(video.url) === false) return false |
166 | isVideoChannelNameValid(videoChannel.name) && | ||
167 | isVideoChannelDescriptionValid(videoChannel.description) && | ||
168 | isDateValid(videoChannel.createdAt) && | ||
169 | isDateValid(videoChannel.updatedAt) && | ||
170 | isUUIDValid(videoChannel.ownerUUID) | ||
171 | } | ||
172 | 104 | ||
173 | function checkRemoveVideoChannel (videoChannel: any) { | 105 | const newUrl = video.url.filter(u => isRemoteVideoUrlValid(u)) |
174 | return isUUIDValid(videoChannel.uuid) | 106 | video.url = newUrl |
175 | } | ||
176 | 107 | ||
177 | function checkAddAuthor (author: any) { | 108 | return true |
178 | return isUUIDValid(author.uuid) && | ||
179 | isVideoAuthorNameValid(author.name) | ||
180 | } | 109 | } |
181 | 110 | ||
182 | function checkRemoveAuthor (author: any) { | 111 | function isRemoteVideoUrlValid (url: any) { |
183 | return isUUIDValid(author.uuid) | 112 | return url.type === 'Link' && |
113 | ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 && | ||
114 | validator.isURL(url.url) && | ||
115 | validator.isInt(url.width, { min: 0 }) && | ||
116 | validator.isInt(url.size, { min: 0 }) | ||
184 | } | 117 | } |