diff options
author | Chocobozzz <me@florianbigard.com> | 2018-02-23 16:39:51 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-02-23 16:44:37 +0100 |
commit | b5c0e95544cec5a33cee3df41c1607d2a0cd5403 (patch) | |
tree | 38a5db1faed107f7b75583c32125152812594821 | |
parent | e3bb78a2134a5e5755b6dbd8987894572ca31269 (diff) | |
download | PeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.tar.gz PeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.tar.zst PeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.zip |
Avoids easy cheating on vidoe views
-rw-r--r-- | server/controllers/api/videos/index.ts | 10 | ||||
-rw-r--r-- | server/initializers/constants.ts | 6 | ||||
-rw-r--r-- | server/lib/redis.ts | 24 | ||||
-rw-r--r-- | server/tests/api/videos/multiple-servers.ts | 17 | ||||
-rw-r--r-- | server/tests/api/videos/single-server.ts | 20 |
5 files changed, 65 insertions, 12 deletions
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index c9334676e..c3d3acd26 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -22,6 +22,7 @@ import { | |||
22 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' | 22 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' |
23 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' | 23 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' |
24 | import { JobQueue } from '../../../lib/job-queue' | 24 | import { JobQueue } from '../../../lib/job-queue' |
25 | import { Redis } from '../../../lib/redis' | ||
25 | import { | 26 | import { |
26 | asyncMiddleware, | 27 | asyncMiddleware, |
27 | authenticate, | 28 | authenticate, |
@@ -352,7 +353,16 @@ function getVideo (req: express.Request, res: express.Response) { | |||
352 | async function viewVideo (req: express.Request, res: express.Response) { | 353 | async function viewVideo (req: express.Request, res: express.Response) { |
353 | const videoInstance = res.locals.video | 354 | const videoInstance = res.locals.video |
354 | 355 | ||
356 | const ip = req.ip | ||
357 | const exists = await Redis.Instance.isViewExists(ip, videoInstance.uuid) | ||
358 | if (exists) { | ||
359 | logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid) | ||
360 | return res.status(204).end() | ||
361 | } | ||
362 | |||
355 | await videoInstance.increment('views') | 363 | await videoInstance.increment('views') |
364 | await Redis.Instance.setView(ip, videoInstance.uuid) | ||
365 | |||
356 | const serverAccount = await getServerActor() | 366 | const serverAccount = await getServerActor() |
357 | 367 | ||
358 | if (videoInstance.isOwned()) { | 368 | if (videoInstance.isOwned()) { |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 328a3e70a..2dc73770d 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -231,6 +231,8 @@ const CONSTRAINTS_FIELDS = { | |||
231 | } | 231 | } |
232 | } | 232 | } |
233 | 233 | ||
234 | let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour | ||
235 | |||
234 | const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { | 236 | const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { |
235 | LIKE: 'like', | 237 | LIKE: 'like', |
236 | DISLIKE: 'dislike' | 238 | DISLIKE: 'dislike' |
@@ -400,6 +402,7 @@ if (isTestInstance() === true) { | |||
400 | ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds | 402 | ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds |
401 | CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB | 403 | CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB |
402 | SCHEDULER_INTERVAL = 10000 | 404 | SCHEDULER_INTERVAL = 10000 |
405 | VIDEO_VIEW_LIFETIME = 1000 // 1 second | ||
403 | } | 406 | } |
404 | 407 | ||
405 | updateWebserverConfig() | 408 | updateWebserverConfig() |
@@ -442,7 +445,8 @@ export { | |||
442 | USER_PASSWORD_RESET_LIFETIME, | 445 | USER_PASSWORD_RESET_LIFETIME, |
443 | IMAGE_MIMETYPE_EXT, | 446 | IMAGE_MIMETYPE_EXT, |
444 | SCHEDULER_INTERVAL, | 447 | SCHEDULER_INTERVAL, |
445 | JOB_COMPLETED_LIFETIME | 448 | JOB_COMPLETED_LIFETIME, |
449 | VIDEO_VIEW_LIFETIME | ||
446 | } | 450 | } |
447 | 451 | ||
448 | // --------------------------------------------------------------------------- | 452 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index 4240cc162..b284cab8f 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { createClient, RedisClient } from 'redis' | 1 | import { createClient, RedisClient } from 'redis' |
2 | import { logger } from '../helpers/logger' | 2 | import { logger } from '../helpers/logger' |
3 | import { generateRandomString } from '../helpers/utils' | 3 | import { generateRandomString } from '../helpers/utils' |
4 | import { CONFIG, USER_PASSWORD_RESET_LIFETIME } from '../initializers' | 4 | import { CONFIG, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers' |
5 | 5 | ||
6 | class Redis { | 6 | class Redis { |
7 | 7 | ||
@@ -46,6 +46,14 @@ class Redis { | |||
46 | return this.getValue(this.generateResetPasswordKey(userId)) | 46 | return this.getValue(this.generateResetPasswordKey(userId)) |
47 | } | 47 | } |
48 | 48 | ||
49 | setView (ip: string, videoUUID: string) { | ||
50 | return this.setValue(this.buildViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) | ||
51 | } | ||
52 | |||
53 | async isViewExists (ip: string, videoUUID: string) { | ||
54 | return this.exists(this.buildViewKey(ip, videoUUID)) | ||
55 | } | ||
56 | |||
49 | private getValue (key: string) { | 57 | private getValue (key: string) { |
50 | return new Promise<string>((res, rej) => { | 58 | return new Promise<string>((res, rej) => { |
51 | this.client.get(this.prefix + key, (err, value) => { | 59 | this.client.get(this.prefix + key, (err, value) => { |
@@ -68,10 +76,24 @@ class Redis { | |||
68 | }) | 76 | }) |
69 | } | 77 | } |
70 | 78 | ||
79 | private exists (key: string) { | ||
80 | return new Promise<boolean>((res, rej) => { | ||
81 | this.client.exists(this.prefix + key, (err, existsNumber) => { | ||
82 | if (err) return rej(err) | ||
83 | |||
84 | return res(existsNumber === 1) | ||
85 | }) | ||
86 | }) | ||
87 | } | ||
88 | |||
71 | private generateResetPasswordKey (userId: number) { | 89 | private generateResetPasswordKey (userId: number) { |
72 | return 'reset-password-' + userId | 90 | return 'reset-password-' + userId |
73 | } | 91 | } |
74 | 92 | ||
93 | private buildViewKey (ip: string, videoUUID: string) { | ||
94 | return videoUUID + '-' + ip | ||
95 | } | ||
96 | |||
75 | static get Instance () { | 97 | static get Instance () { |
76 | return this.instance || (this.instance = new this()) | 98 | return this.instance || (this.instance = new this()) |
77 | } | 99 | } |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index c82ac1348..27c4c30b8 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -421,15 +421,22 @@ describe('Test multiple servers', function () { | |||
421 | }) | 421 | }) |
422 | 422 | ||
423 | it('Should view multiple videos on owned servers', async function () { | 423 | it('Should view multiple videos on owned servers', async function () { |
424 | this.timeout(10000) | 424 | this.timeout(15000) |
425 | 425 | ||
426 | const tasks: Promise<any>[] = [] | 426 | const tasks: Promise<any>[] = [] |
427 | tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) | 427 | await viewVideo(servers[2].url, localVideosServer3[0]) |
428 | tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) | 428 | await viewVideo(servers[2].url, localVideosServer3[0]) |
429 | tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) | 429 | await viewVideo(servers[2].url, localVideosServer3[0]) |
430 | tasks.push(viewVideo(servers[2].url, localVideosServer3[1])) | 430 | await viewVideo(servers[2].url, localVideosServer3[1]) |
431 | 431 | ||
432 | await Promise.all(tasks) | 432 | await Promise.all(tasks) |
433 | await wait(1500) | ||
434 | |||
435 | await viewVideo(servers[2].url, localVideosServer3[0]) | ||
436 | |||
437 | await wait(1500) | ||
438 | |||
439 | await viewVideo(servers[2].url, localVideosServer3[0]) | ||
433 | 440 | ||
434 | await wait(5000) | 441 | await wait(5000) |
435 | 442 | ||
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 83b6a0e9a..cf2721838 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts | |||
@@ -8,7 +8,7 @@ import { | |||
8 | checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, | 8 | checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, |
9 | getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer, | 9 | getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer, |
10 | searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo, | 10 | searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo, |
11 | viewVideo | 11 | viewVideo, wait |
12 | } from '../../utils' | 12 | } from '../../utils' |
13 | 13 | ||
14 | const expect = chai.expect | 14 | const expect = chai.expect |
@@ -149,8 +149,7 @@ describe('Test a single server', function () { | |||
149 | }) | 149 | }) |
150 | 150 | ||
151 | it('Should get and seed the uploaded video', async function () { | 151 | it('Should get and seed the uploaded video', async function () { |
152 | // Yes, this could be long | 152 | this.timeout(5000) |
153 | this.timeout(60000) | ||
154 | 153 | ||
155 | const res = await getVideosList(server.url) | 154 | const res = await getVideosList(server.url) |
156 | 155 | ||
@@ -163,8 +162,7 @@ describe('Test a single server', function () { | |||
163 | }) | 162 | }) |
164 | 163 | ||
165 | it('Should get the video by UUID', async function () { | 164 | it('Should get the video by UUID', async function () { |
166 | // Yes, this could be long | 165 | this.timeout(5000) |
167 | this.timeout(60000) | ||
168 | 166 | ||
169 | const res = await getVideo(server.url, videoUUID) | 167 | const res = await getVideo(server.url, videoUUID) |
170 | 168 | ||
@@ -173,10 +171,22 @@ describe('Test a single server', function () { | |||
173 | }) | 171 | }) |
174 | 172 | ||
175 | it('Should have the views updated', async function () { | 173 | it('Should have the views updated', async function () { |
174 | this.timeout(10000) | ||
175 | |||
176 | await viewVideo(server.url, videoId) | 176 | await viewVideo(server.url, videoId) |
177 | await viewVideo(server.url, videoId) | 177 | await viewVideo(server.url, videoId) |
178 | await viewVideo(server.url, videoId) | 178 | await viewVideo(server.url, videoId) |
179 | 179 | ||
180 | await wait(1500) | ||
181 | |||
182 | await viewVideo(server.url, videoId) | ||
183 | await viewVideo(server.url, videoId) | ||
184 | |||
185 | await wait(1500) | ||
186 | |||
187 | await viewVideo(server.url, videoId) | ||
188 | await viewVideo(server.url, videoId) | ||
189 | |||
180 | const res = await getVideo(server.url, videoId) | 190 | const res = await getVideo(server.url, videoId) |
181 | 191 | ||
182 | const video = res.body | 192 | const video = res.body |