From b44164bb567fe7c9f65f1ac2908d44990a8ccc8e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Oct 2018 13:03:04 +0200 Subject: Add ability to mute a user/instance by server in server api --- server/controllers/api/server/index.ts | 2 + server/controllers/api/server/server-blocklist.ts | 132 ++++++ server/controllers/api/users/my-blocklist.ts | 8 +- server/middlewares/validators/blocklist.ts | 45 +- server/models/utils.ts | 2 - server/tests/api/check-params/blocklist.ts | 256 ++++++++++- server/tests/api/users/account-blocklist.ts | 294 ------------- server/tests/api/users/blocklist.ts | 500 ++++++++++++++++++++++ server/tests/api/users/index.ts | 1 + server/tests/utils/users/blocklist.ts | 97 ++++- shared/models/users/user-right.enum.ts | 3 + shared/models/users/user-role.ts | 4 +- 12 files changed, 1035 insertions(+), 309 deletions(-) create mode 100644 server/controllers/api/server/server-blocklist.ts delete mode 100644 server/tests/api/users/account-blocklist.ts create mode 100644 server/tests/api/users/blocklist.ts diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts index 43bca2c10..c08192a8c 100644 --- a/server/controllers/api/server/index.ts +++ b/server/controllers/api/server/index.ts @@ -2,12 +2,14 @@ import * as express from 'express' import { serverFollowsRouter } from './follows' import { statsRouter } from './stats' import { serverRedundancyRouter } from './redundancy' +import { serverBlocklistRouter } from './server-blocklist' const serverRouter = express.Router() serverRouter.use('/', serverFollowsRouter) serverRouter.use('/', serverRedundancyRouter) serverRouter.use('/', statsRouter) +serverRouter.use('/', serverBlocklistRouter) // --------------------------------------------------------------------------- diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts new file mode 100644 index 000000000..3cb3a96e2 --- /dev/null +++ b/server/controllers/api/server/server-blocklist.ts @@ -0,0 +1,132 @@ +import * as express from 'express' +import 'multer' +import { getFormattedObjects, getServerActor } from '../../../helpers/utils' +import { + asyncMiddleware, + asyncRetryTransactionMiddleware, + authenticate, + ensureUserHasRight, + paginationValidator, + setDefaultPagination, + setDefaultSort +} from '../../../middlewares' +import { + accountsBlocklistSortValidator, + blockAccountValidator, + blockServerValidator, + serversBlocklistSortValidator, + unblockAccountByServerValidator, + unblockServerByServerValidator +} from '../../../middlewares/validators' +import { AccountModel } from '../../../models/account/account' +import { AccountBlocklistModel } from '../../../models/account/account-blocklist' +import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' +import { ServerBlocklistModel } from '../../../models/server/server-blocklist' +import { ServerModel } from '../../../models/server/server' +import { UserRight } from '../../../../shared/models/users' + +const serverBlocklistRouter = express.Router() + +serverBlocklistRouter.get('/blocklist/accounts', + authenticate, + ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST), + paginationValidator, + accountsBlocklistSortValidator, + setDefaultSort, + setDefaultPagination, + asyncMiddleware(listBlockedAccounts) +) + +serverBlocklistRouter.post('/blocklist/accounts', + authenticate, + ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST), + asyncMiddleware(blockAccountValidator), + asyncRetryTransactionMiddleware(blockAccount) +) + +serverBlocklistRouter.delete('/blocklist/accounts/:accountName', + authenticate, + ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST), + asyncMiddleware(unblockAccountByServerValidator), + asyncRetryTransactionMiddleware(unblockAccount) +) + +serverBlocklistRouter.get('/blocklist/servers', + authenticate, + ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST), + paginationValidator, + serversBlocklistSortValidator, + setDefaultSort, + setDefaultPagination, + asyncMiddleware(listBlockedServers) +) + +serverBlocklistRouter.post('/blocklist/servers', + authenticate, + ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST), + asyncMiddleware(blockServerValidator), + asyncRetryTransactionMiddleware(blockServer) +) + +serverBlocklistRouter.delete('/blocklist/servers/:host', + authenticate, + ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST), + asyncMiddleware(unblockServerByServerValidator), + asyncRetryTransactionMiddleware(unblockServer) +) + +export { + serverBlocklistRouter +} + +// --------------------------------------------------------------------------- + +async function listBlockedAccounts (req: express.Request, res: express.Response) { + const serverActor = await getServerActor() + + const resultList = await AccountBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) + + return res.json(getFormattedObjects(resultList.data, resultList.total)) +} + +async function blockAccount (req: express.Request, res: express.Response) { + const serverActor = await getServerActor() + const accountToBlock: AccountModel = res.locals.account + + await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id) + + return res.status(204).end() +} + +async function unblockAccount (req: express.Request, res: express.Response) { + const accountBlock: AccountBlocklistModel = res.locals.accountBlock + + await removeAccountFromBlocklist(accountBlock) + + return res.status(204).end() +} + +async function listBlockedServers (req: express.Request, res: express.Response) { + const serverActor = await getServerActor() + + const resultList = await ServerBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) + + return res.json(getFormattedObjects(resultList.data, resultList.total)) +} + +async function blockServer (req: express.Request, res: express.Response) { + const serverActor = await getServerActor() + const serverToBlock: ServerModel = res.locals.server + + await addServerInBlocklist(serverActor.Account.id, serverToBlock.id) + + return res.status(204).end() +} + +async function unblockServer (req: express.Request, res: express.Response) { + const serverBlock: ServerBlocklistModel = res.locals.serverBlock + + await removeServerFromBlocklist(serverBlock) + + return res.status(204).end() +} diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts index 95a4105ec..9575eab46 100644 --- a/server/controllers/api/users/my-blocklist.ts +++ b/server/controllers/api/users/my-blocklist.ts @@ -12,8 +12,8 @@ import { } from '../../../middlewares' import { accountsBlocklistSortValidator, - blockAccountByAccountValidator, - blockServerByAccountValidator, + blockAccountValidator, + blockServerValidator, serversBlocklistSortValidator, unblockServerByAccountValidator } from '../../../middlewares/validators' @@ -37,7 +37,7 @@ myBlocklistRouter.get('/me/blocklist/accounts', myBlocklistRouter.post('/me/blocklist/accounts', authenticate, - asyncMiddleware(blockAccountByAccountValidator), + asyncMiddleware(blockAccountValidator), asyncRetryTransactionMiddleware(blockAccount) ) @@ -58,7 +58,7 @@ myBlocklistRouter.get('/me/blocklist/servers', myBlocklistRouter.post('/me/blocklist/servers', authenticate, - asyncMiddleware(blockServerByAccountValidator), + asyncMiddleware(blockServerValidator), asyncRetryTransactionMiddleware(blockServer) ) diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index 25c054d6b..109276c63 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts @@ -9,8 +9,9 @@ import { isHostValid } from '../../helpers/custom-validators/servers' import { ServerBlocklistModel } from '../../models/server/server-blocklist' import { ServerModel } from '../../models/server/server' import { CONFIG } from '../../initializers' +import { getServerActor } from '../../helpers/utils' -const blockAccountByAccountValidator = [ +const blockAccountValidator = [ body('accountName').exists().withMessage('Should have an account name with host'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { @@ -51,7 +52,24 @@ const unblockAccountByAccountValidator = [ } ] -const blockServerByAccountValidator = [ +const unblockAccountByServerValidator = [ + param('accountName').exists().withMessage('Should have an account name with host'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking unblockAccountByServerValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isAccountNameWithHostExist(req.params.accountName, res)) return + + const serverActor = await getServerActor() + const targetAccount = res.locals.account + if (!await isUnblockAccountExists(serverActor.Account.id, targetAccount.id, res)) return + + return next() + } +] + +const blockServerValidator = [ body('host').custom(isHostValid).withMessage('Should have a valid host'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { @@ -95,13 +113,30 @@ const unblockServerByAccountValidator = [ } ] +const unblockServerByServerValidator = [ + param('host').custom(isHostValid).withMessage('Should have an account name with host'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking unblockServerByServerValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + const serverActor = await getServerActor() + if (!await isUnblockServerExists(serverActor.Account.id, req.params.host, res)) return + + return next() + } +] + // --------------------------------------------------------------------------- export { - blockServerByAccountValidator, - blockAccountByAccountValidator, + blockServerValidator, + blockAccountValidator, unblockAccountByAccountValidator, - unblockServerByAccountValidator + unblockServerByAccountValidator, + unblockAccountByServerValidator, + unblockServerByServerValidator } // --------------------------------------------------------------------------- diff --git a/server/models/utils.ts b/server/models/utils.ts index 50c865e75..60b0906e8 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -72,8 +72,6 @@ function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number const query = 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + ' UNION ALL ' + - // 'SELECT "accountId" FROM "accountBlocklist" WHERE "targetAccountId" = user.account.id - // UNION ALL 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts index d24d9323f..c745ac975 100644 --- a/server/tests/api/check-params/blocklist.ts +++ b/server/tests/api/check-params/blocklist.ts @@ -12,13 +12,14 @@ import { makeGetRequest, makePostBodyRequest, ServerInfo, - setAccessTokensToServers + setAccessTokensToServers, userLogin } from '../../utils' import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' describe('Test blocklist API validators', function () { let servers: ServerInfo[] let server: ServerInfo + let userAccessToken: string before(async function () { this.timeout(60000) @@ -33,15 +34,17 @@ describe('Test blocklist API validators', function () { const user = { username: 'user1', password: 'password' } await createUser(server.url, server.accessToken, user.username, user.password) + userAccessToken = await userLogin(server, user) + await doubleFollow(servers[0], servers[1]) }) // --------------------------------------------------------------- describe('When managing user blocklist', function () { - const path = '/api/v1/users/me/blocklist/accounts' describe('When managing user accounts blocklist', function () { + const path = '/api/v1/users/me/blocklist/accounts' describe('When listing blocked accounts', function () { it('Should fail with an unauthenticated user', async function () { @@ -231,6 +234,255 @@ describe('Test blocklist API validators', function () { }) }) + describe('When managing server blocklist', function () { + + describe('When managing server accounts blocklist', function () { + const path = '/api/v1/server/blocklist/accounts' + + describe('When listing blocked accounts', function () { + it('Should fail with an unauthenticated user', async function () { + await makeGetRequest({ + url: server.url, + path, + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makeGetRequest({ + url: server.url, + token: userAccessToken, + path, + statusCodeExpected: 403 + }) + }) + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, server.accessToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, server.accessToken) + }) + }) + + describe('When blocking an account', function () { + it('Should fail with an unauthenticated user', async function () { + await makePostBodyRequest({ + url: server.url, + path, + fields: { accountName: 'user1' }, + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makePostBodyRequest({ + url: server.url, + token: userAccessToken, + path, + fields: { accountName: 'user1' }, + statusCodeExpected: 403 + }) + }) + + it('Should fail with an unknown account', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { accountName: 'user2' }, + statusCodeExpected: 404 + }) + }) + + it('Should fail to block ourselves', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { accountName: 'root' }, + statusCodeExpected: 409 + }) + }) + + it('Should succeed with the correct params', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { accountName: 'user1' }, + statusCodeExpected: 204 + }) + }) + }) + + describe('When unblocking an account', function () { + it('Should fail with an unauthenticated user', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/user1', + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/user1', + token: userAccessToken, + statusCodeExpected: 403 + }) + }) + + it('Should fail with an unknown account block', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/user2', + token: server.accessToken, + statusCodeExpected: 404 + }) + }) + + it('Should succeed with the correct params', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/user1', + token: server.accessToken, + statusCodeExpected: 204 + }) + }) + }) + }) + + describe('When managing server servers blocklist', function () { + const path = '/api/v1/server/blocklist/servers' + + describe('When listing blocked servers', function () { + it('Should fail with an unauthenticated user', async function () { + await makeGetRequest({ + url: server.url, + path, + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makeGetRequest({ + url: server.url, + token: userAccessToken, + path, + statusCodeExpected: 403 + }) + }) + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, server.accessToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, server.accessToken) + }) + }) + + describe('When blocking a server', function () { + it('Should fail with an unauthenticated user', async function () { + await makePostBodyRequest({ + url: server.url, + path, + fields: { host: 'localhost:9002' }, + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makePostBodyRequest({ + url: server.url, + token: userAccessToken, + path, + fields: { host: 'localhost:9002' }, + statusCodeExpected: 403 + }) + }) + + it('Should fail with an unknown server', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { host: 'localhost:9003' }, + statusCodeExpected: 404 + }) + }) + + it('Should fail with our own server', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { host: 'localhost:9001' }, + statusCodeExpected: 409 + }) + }) + + it('Should succeed with the correct params', async function () { + await makePostBodyRequest({ + url: server.url, + token: server.accessToken, + path, + fields: { host: 'localhost:9002' }, + statusCodeExpected: 204 + }) + }) + }) + + describe('When unblocking a server', function () { + it('Should fail with an unauthenticated user', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/localhost:9002', + statusCodeExpected: 401 + }) + }) + + it('Should fail with a user without the appropriate rights', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/localhost:9002', + token: userAccessToken, + statusCodeExpected: 403 + }) + }) + + it('Should fail with an unknown server block', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/localhost:9003', + token: server.accessToken, + statusCodeExpected: 404 + }) + }) + + it('Should succeed with the correct params', async function () { + await makeDeleteRequest({ + url: server.url, + path: path + '/localhost:9002', + token: server.accessToken, + statusCodeExpected: 204 + }) + }) + }) + }) + }) + after(async function () { killallServers(servers) diff --git a/server/tests/api/users/account-blocklist.ts b/server/tests/api/users/account-blocklist.ts deleted file mode 100644 index 026971331..000000000 --- a/server/tests/api/users/account-blocklist.ts +++ /dev/null @@ -1,294 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import * as chai from 'chai' -import 'mocha' -import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' -import { - createUser, - doubleFollow, - flushAndRunMultipleServers, - flushTests, - killallServers, - ServerInfo, - uploadVideo, - userLogin -} from '../../utils/index' -import { setAccessTokensToServers } from '../../utils/users/login' -import { getVideosListWithToken } from '../../utils/videos/videos' -import { - addVideoCommentReply, - addVideoCommentThread, - getVideoCommentThreads, - getVideoThreadComments -} from '../../utils/videos/video-comments' -import { waitJobs } from '../../utils/server/jobs' -import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' -import { - addAccountToAccountBlocklist, - addServerToAccountBlocklist, - getAccountBlocklistByAccount, getServerBlocklistByAccount, - removeAccountFromAccountBlocklist, - removeServerFromAccountBlocklist -} from '../../utils/users/blocklist' - -const expect = chai.expect - -async function checkAllVideos (url: string, token: string) { - const res = await getVideosListWithToken(url, token) - - expect(res.body.data).to.have.lengthOf(4) -} - -async function checkAllComments (url: string, token: string, videoUUID: string) { - const resThreads = await getVideoCommentThreads(url, videoUUID, 0, 5, '-createdAt', token) - - const threads: VideoComment[] = resThreads.body.data - expect(threads).to.have.lengthOf(2) - - for (const thread of threads) { - const res = await getVideoThreadComments(url, videoUUID, thread.id, token) - - const tree: VideoCommentThreadTree = res.body - expect(tree.children).to.have.lengthOf(1) - } -} - -describe('Test accounts blocklist', function () { - let servers: ServerInfo[] - let videoUUID1: string - let videoUUID2: string - let userToken1: string - let userToken2: string - - before(async function () { - this.timeout(60000) - - await flushTests() - - servers = await flushAndRunMultipleServers(2) - await setAccessTokensToServers(servers) - - { - const user = { username: 'user1', password: 'password' } - await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) - - userToken1 = await userLogin(servers[0], user) - await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) - } - - { - const user = { username: 'user2', password: 'password' } - await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) - - userToken2 = await userLogin(servers[1], user) - await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) - } - - { - const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' }) - videoUUID1 = res.body.video.uuid - } - - { - const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' }) - videoUUID2 = res.body.video.uuid - } - - await doubleFollow(servers[0], servers[1]) - - { - const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1') - const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') - await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') - } - - { - const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1') - await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') - } - - await waitJobs(servers) - }) - - describe('When managing account blocklist', function () { - it('Should list all videos', function () { - return checkAllVideos(servers[0].url, servers[0].accessToken) - }) - - it('Should list the comments', function () { - return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) - }) - - it('Should block a remote account', async function () { - await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:9002') - }) - - it('Should hide its videos', async function () { - const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) - - const videos: Video[] = res.body.data - expect(videos).to.have.lengthOf(3) - - const v = videos.find(v => v.name === 'video user 2') - expect(v).to.be.undefined - }) - - it('Should block a local account', async function () { - await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1') - }) - - it('Should hide its videos', async function () { - const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) - - const videos: Video[] = res.body.data - expect(videos).to.have.lengthOf(2) - - const v = videos.find(v => v.name === 'video user 1') - expect(v).to.be.undefined - }) - - it('Should hide its comments', async function () { - const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', servers[0].accessToken) - - const threads: VideoComment[] = resThreads.body.data - expect(threads).to.have.lengthOf(1) - expect(threads[0].totalReplies).to.equal(0) - - const t = threads.find(t => t.text === 'comment user 1') - expect(t).to.be.undefined - - for (const thread of threads) { - const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, servers[0].accessToken) - - const tree: VideoCommentThreadTree = res.body - expect(tree.children).to.have.lengthOf(0) - } - }) - - it('Should list all the videos with another user', async function () { - return checkAllVideos(servers[0].url, userToken1) - }) - - it('Should list all the comments with another user', async function () { - return checkAllComments(servers[0].url, userToken1, videoUUID1) - }) - - it('Should list blocked accounts', async function () { - { - const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') - const blocks: AccountBlock[] = res.body.data - - expect(res.body.total).to.equal(2) - - const block = blocks[0] - expect(block.byAccount.displayName).to.equal('root') - expect(block.byAccount.name).to.equal('root') - expect(block.blockedAccount.displayName).to.equal('user2') - expect(block.blockedAccount.name).to.equal('user2') - expect(block.blockedAccount.host).to.equal('localhost:9002') - } - - { - const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') - const blocks: AccountBlock[] = res.body.data - - expect(res.body.total).to.equal(2) - - const block = blocks[0] - expect(block.byAccount.displayName).to.equal('root') - expect(block.byAccount.name).to.equal('root') - expect(block.blockedAccount.displayName).to.equal('user1') - expect(block.blockedAccount.name).to.equal('user1') - expect(block.blockedAccount.host).to.equal('localhost:9001') - } - }) - - it('Should unblock the remote account', async function () { - await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:9002') - }) - - it('Should display its videos', async function () { - const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) - - const videos: Video[] = res.body.data - expect(videos).to.have.lengthOf(3) - - const v = videos.find(v => v.name === 'video user 2') - expect(v).not.to.be.undefined - }) - - it('Should unblock the local account', async function () { - await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1') - }) - - it('Should display its comments', function () { - return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) - }) - }) - - describe('When managing server blocklist', function () { - it('Should list all videos', function () { - return checkAllVideos(servers[0].url, servers[0].accessToken) - }) - - it('Should list the comments', function () { - return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) - }) - - it('Should block a remote server', async function () { - await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:9002') - }) - - it('Should hide its videos', async function () { - const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) - - const videos: Video[] = res.body.data - expect(videos).to.have.lengthOf(2) - - const v1 = videos.find(v => v.name === 'video user 2') - const v2 = videos.find(v => v.name === 'video server 2') - - expect(v1).to.be.undefined - expect(v2).to.be.undefined - }) - - it('Should list all the videos with another user', async function () { - return checkAllVideos(servers[0].url, userToken1) - }) - - it('Should hide its comments') - - it('Should list blocked servers', async function () { - const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') - const blocks: ServerBlock[] = res.body.data - - expect(res.body.total).to.equal(1) - - const block = blocks[0] - expect(block.byAccount.displayName).to.equal('root') - expect(block.byAccount.name).to.equal('root') - expect(block.blockedServer.host).to.equal('localhost:9002') - }) - - it('Should unblock the remote server', async function () { - await removeServerFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:9002') - }) - - it('Should display its videos', function () { - return checkAllVideos(servers[0].url, servers[0].accessToken) - }) - - it('Should display its comments', function () { - return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) - }) - }) - - after(async function () { - killallServers(servers) - - // Keep the logs if the test failed - if (this[ 'ok' ]) { - await flushTests() - } - }) -}) diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts new file mode 100644 index 000000000..99fe04b8c --- /dev/null +++ b/server/tests/api/users/blocklist.ts @@ -0,0 +1,500 @@ +/* tslint:disable:no-unused-expression */ + +import * as chai from 'chai' +import 'mocha' +import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' +import { + createUser, + doubleFollow, + flushAndRunMultipleServers, + flushTests, + killallServers, + ServerInfo, + uploadVideo, + userLogin +} from '../../utils/index' +import { setAccessTokensToServers } from '../../utils/users/login' +import { getVideosListWithToken } from '../../utils/videos/videos' +import { + addVideoCommentReply, + addVideoCommentThread, + getVideoCommentThreads, + getVideoThreadComments +} from '../../utils/videos/video-comments' +import { waitJobs } from '../../utils/server/jobs' +import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' +import { + addAccountToAccountBlocklist, + addAccountToServerBlocklist, + addServerToAccountBlocklist, + addServerToServerBlocklist, + getAccountBlocklistByAccount, + getAccountBlocklistByServer, + getServerBlocklistByAccount, + getServerBlocklistByServer, + removeAccountFromAccountBlocklist, + removeAccountFromServerBlocklist, + removeServerFromAccountBlocklist, + removeServerFromServerBlocklist +} from '../../utils/users/blocklist' + +const expect = chai.expect + +async function checkAllVideos (url: string, token: string) { + const res = await getVideosListWithToken(url, token) + + expect(res.body.data).to.have.lengthOf(4) +} + +async function checkAllComments (url: string, token: string, videoUUID: string) { + const resThreads = await getVideoCommentThreads(url, videoUUID, 0, 5, '-createdAt', token) + + const threads: VideoComment[] = resThreads.body.data + expect(threads).to.have.lengthOf(2) + + for (const thread of threads) { + const res = await getVideoThreadComments(url, videoUUID, thread.id, token) + + const tree: VideoCommentThreadTree = res.body + expect(tree.children).to.have.lengthOf(1) + } +} + +describe('Test blocklist', function () { + let servers: ServerInfo[] + let videoUUID1: string + let videoUUID2: string + let userToken1: string + let userModeratorToken: string + let userToken2: string + + before(async function () { + this.timeout(60000) + + await flushTests() + + servers = await flushAndRunMultipleServers(2) + await setAccessTokensToServers(servers) + + { + const user = { username: 'user1', password: 'password' } + await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) + + userToken1 = await userLogin(servers[0], user) + await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) + } + + { + const user = { username: 'moderator', password: 'password' } + await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) + + userModeratorToken = await userLogin(servers[0], user) + } + + { + const user = { username: 'user2', password: 'password' } + await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) + + userToken2 = await userLogin(servers[1], user) + await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) + } + + { + const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' }) + videoUUID1 = res.body.video.uuid + } + + { + const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' }) + videoUUID2 = res.body.video.uuid + } + + await doubleFollow(servers[0], servers[1]) + + { + const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1') + const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') + await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') + } + + { + const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1') + await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') + } + + await waitJobs(servers) + }) + + describe('User blocklist', function () { + + describe('When managing account blocklist', function () { + it('Should list all videos', function () { + return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) + }) + + it('Should list the comments', function () { + return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) + }) + + it('Should block a remote account', async function () { + await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') + }) + + it('Should hide its videos', async function () { + const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(3) + + const v = videos.find(v => v.name === 'video user 2') + expect(v).to.be.undefined + }) + + it('Should block a local account', async function () { + await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') + }) + + it('Should hide its videos', async function () { + const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(2) + + const v = videos.find(v => v.name === 'video user 1') + expect(v).to.be.undefined + }) + + it('Should hide its comments', async function () { + const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', servers[ 0 ].accessToken) + + const threads: VideoComment[] = resThreads.body.data + expect(threads).to.have.lengthOf(1) + expect(threads[ 0 ].totalReplies).to.equal(0) + + const t = threads.find(t => t.text === 'comment user 1') + expect(t).to.be.undefined + + for (const thread of threads) { + const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, servers[ 0 ].accessToken) + + const tree: VideoCommentThreadTree = res.body + expect(tree.children).to.have.lengthOf(0) + } + }) + + it('Should list all the videos with another user', async function () { + return checkAllVideos(servers[ 0 ].url, userToken1) + }) + + it('Should list all the comments with another user', async function () { + return checkAllComments(servers[ 0 ].url, userToken1, videoUUID1) + }) + + it('Should list blocked accounts', async function () { + { + const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') + const blocks: AccountBlock[] = res.body.data + + expect(res.body.total).to.equal(2) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('root') + expect(block.byAccount.name).to.equal('root') + expect(block.blockedAccount.displayName).to.equal('user2') + expect(block.blockedAccount.name).to.equal('user2') + expect(block.blockedAccount.host).to.equal('localhost:9002') + } + + { + const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') + const blocks: AccountBlock[] = res.body.data + + expect(res.body.total).to.equal(2) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('root') + expect(block.byAccount.name).to.equal('root') + expect(block.blockedAccount.displayName).to.equal('user1') + expect(block.blockedAccount.name).to.equal('user1') + expect(block.blockedAccount.host).to.equal('localhost:9001') + } + }) + + it('Should unblock the remote account', async function () { + await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') + }) + + it('Should display its videos', async function () { + const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(3) + + const v = videos.find(v => v.name === 'video user 2') + expect(v).not.to.be.undefined + }) + + it('Should unblock the local account', async function () { + await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') + }) + + it('Should display its comments', function () { + return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) + }) + }) + + describe('When managing server blocklist', function () { + it('Should list all videos', function () { + return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) + }) + + it('Should list the comments', function () { + return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) + }) + + it('Should block a remote server', async function () { + await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') + }) + + it('Should hide its videos', async function () { + const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(2) + + const v1 = videos.find(v => v.name === 'video user 2') + const v2 = videos.find(v => v.name === 'video server 2') + + expect(v1).to.be.undefined + expect(v2).to.be.undefined + }) + + it('Should list all the videos with another user', async function () { + return checkAllVideos(servers[ 0 ].url, userToken1) + }) + + it('Should hide its comments') + + it('Should list blocked servers', async function () { + const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') + const blocks: ServerBlock[] = res.body.data + + expect(res.body.total).to.equal(1) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('root') + expect(block.byAccount.name).to.equal('root') + expect(block.blockedServer.host).to.equal('localhost:9002') + }) + + it('Should unblock the remote server', async function () { + await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') + }) + + it('Should display its videos', function () { + return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) + }) + + it('Should display its comments', function () { + return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) + }) + }) + }) + + describe('Server blocklist', function () { + + describe('When managing account blocklist', function () { + it('Should list all videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllVideos(servers[ 0 ].url, token) + } + }) + + it('Should list the comments', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllComments(servers[ 0 ].url, token, videoUUID1) + } + }) + + it('Should block a remote account', async function () { + await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') + }) + + it('Should hide its videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + const res = await getVideosListWithToken(servers[ 0 ].url, token) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(3) + + const v = videos.find(v => v.name === 'video user 2') + expect(v).to.be.undefined + } + }) + + it('Should block a local account', async function () { + await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') + }) + + it('Should hide its videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + const res = await getVideosListWithToken(servers[ 0 ].url, token) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(2) + + const v = videos.find(v => v.name === 'video user 1') + expect(v).to.be.undefined + } + }) + + it('Should hide its comments', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', token) + + const threads: VideoComment[] = resThreads.body.data + expect(threads).to.have.lengthOf(1) + expect(threads[ 0 ].totalReplies).to.equal(0) + + const t = threads.find(t => t.text === 'comment user 1') + expect(t).to.be.undefined + + for (const thread of threads) { + const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, token) + + const tree: VideoCommentThreadTree = res.body + expect(tree.children).to.have.lengthOf(0) + } + } + }) + + it('Should list blocked accounts', async function () { + { + const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') + const blocks: AccountBlock[] = res.body.data + + expect(res.body.total).to.equal(2) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('peertube') + expect(block.byAccount.name).to.equal('peertube') + expect(block.blockedAccount.displayName).to.equal('user2') + expect(block.blockedAccount.name).to.equal('user2') + expect(block.blockedAccount.host).to.equal('localhost:9002') + } + + { + const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') + const blocks: AccountBlock[] = res.body.data + + expect(res.body.total).to.equal(2) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('peertube') + expect(block.byAccount.name).to.equal('peertube') + expect(block.blockedAccount.displayName).to.equal('user1') + expect(block.blockedAccount.name).to.equal('user1') + expect(block.blockedAccount.host).to.equal('localhost:9001') + } + }) + + it('Should unblock the remote account', async function () { + await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') + }) + + it('Should display its videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + const res = await getVideosListWithToken(servers[ 0 ].url, token) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(3) + + const v = videos.find(v => v.name === 'video user 2') + expect(v).not.to.be.undefined + } + }) + + it('Should unblock the local account', async function () { + await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') + }) + + it('Should display its comments', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllComments(servers[ 0 ].url, token, videoUUID1) + } + }) + }) + + describe('When managing server blocklist', function () { + it('Should list all videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllVideos(servers[ 0 ].url, token) + } + }) + + it('Should list the comments', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllComments(servers[ 0 ].url, token, videoUUID1) + } + }) + + it('Should block a remote server', async function () { + await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') + }) + + it('Should hide its videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + const res = await getVideosListWithToken(servers[ 0 ].url, token) + + const videos: Video[] = res.body.data + expect(videos).to.have.lengthOf(2) + + const v1 = videos.find(v => v.name === 'video user 2') + const v2 = videos.find(v => v.name === 'video server 2') + + expect(v1).to.be.undefined + expect(v2).to.be.undefined + } + }) + + it('Should hide its comments') + + it('Should list blocked servers', async function () { + const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') + const blocks: ServerBlock[] = res.body.data + + expect(res.body.total).to.equal(1) + + const block = blocks[ 0 ] + expect(block.byAccount.displayName).to.equal('peertube') + expect(block.byAccount.name).to.equal('peertube') + expect(block.blockedServer.host).to.equal('localhost:9002') + }) + + it('Should unblock the remote server', async function () { + await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') + }) + + it('Should list all videos', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllVideos(servers[ 0 ].url, token) + } + }) + + it('Should list the comments', async function () { + for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { + await checkAllComments(servers[ 0 ].url, token, videoUUID1) + } + }) + }) + }) + + after(async function () { + killallServers(servers) + + // Keep the logs if the test failed + if (this[ 'ok' ]) { + await flushTests() + } + }) +}) diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts index 21d75da3e..0a1b8b0b2 100644 --- a/server/tests/api/users/index.ts +++ b/server/tests/api/users/index.ts @@ -1,3 +1,4 @@ +import './blocklist' import './user-subscriptions' import './users' import './users-verification' diff --git a/server/tests/utils/users/blocklist.ts b/server/tests/utils/users/blocklist.ts index 47b315480..35b537571 100644 --- a/server/tests/utils/users/blocklist.ts +++ b/server/tests/utils/users/blocklist.ts @@ -91,6 +91,94 @@ function removeServerFromAccountBlocklist (url: string, token: string, serverToB }) } +function getAccountBlocklistByServer ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/server/blocklist/accounts' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addAccountToServerBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/accounts' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + accountName: accountToBlock + }, + statusCodeExpected + }) +} + +function removeAccountFromServerBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function getServerBlocklistByServer ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/server/blocklist/servers' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addServerToServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/servers' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + host: serverToBlock + }, + statusCodeExpected + }) +} + +function removeServerFromServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/servers/' + serverToBlock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + // --------------------------------------------------------------------------- export { @@ -99,5 +187,12 @@ export { removeAccountFromAccountBlocklist, getServerBlocklistByAccount, addServerToAccountBlocklist, - removeServerFromAccountBlocklist + removeServerFromAccountBlocklist, + + getAccountBlocklistByServer, + addAccountToServerBlocklist, + removeAccountFromServerBlocklist, + getServerBlocklistByServer, + addServerToServerBlocklist, + removeServerFromServerBlocklist } diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index ed2c536ce..51c59d20a 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts @@ -8,6 +8,9 @@ export enum UserRight { MANAGE_JOBS, MANAGE_CONFIGURATION, + MANAGE_ACCOUNTS_BLOCKLIST, + MANAGE_SERVERS_BLOCKLIST, + MANAGE_VIDEO_BLACKLIST, REMOVE_ANY_VIDEO, diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index d7020c0f2..adef8fd95 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts @@ -27,7 +27,9 @@ const userRoleRights: { [ id: number ]: UserRight[] } = { UserRight.REMOVE_ANY_VIDEO_CHANNEL, UserRight.REMOVE_ANY_VIDEO_COMMENT, UserRight.UPDATE_ANY_VIDEO, - UserRight.SEE_ALL_VIDEOS + UserRight.SEE_ALL_VIDEOS, + UserRight.MANAGE_ACCOUNTS_BLOCKLIST, + UserRight.MANAGE_SERVERS_BLOCKLIST ], [UserRole.USER]: [] -- cgit v1.2.3