Add ability to remove an instance follower in API

pull/1765/head
Chocobozzz 2019-04-08 11:52:29 +02:00
parent ae9bbed46d
commit 0e9c48c2ed
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 202 additions and 11 deletions

View File

@ -3,18 +3,23 @@ import { UserRight } from '../../../../shared/models/users'
import { logger } from '../../../helpers/logger'
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers'
import { sendUndoFollow } from '../../../lib/activitypub/send'
import { sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
import {
asyncMiddleware,
authenticate,
ensureUserHasRight,
paginationValidator,
removeFollowingValidator,
setBodyHostsPort,
setDefaultPagination,
setDefaultSort
} from '../../../middlewares'
import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators'
import {
followersSortValidator,
followingSortValidator,
followValidator,
removeFollowerValidator,
removeFollowingValidator
} from '../../../middlewares/validators'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { JobQueue } from '../../../lib/job-queue'
import { removeRedundancyOf } from '../../../lib/redundancy'
@ -40,7 +45,7 @@ serverFollowsRouter.delete('/following/:host',
authenticate,
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
asyncMiddleware(removeFollowingValidator),
asyncMiddleware(removeFollow)
asyncMiddleware(removeFollowing)
)
serverFollowsRouter.get('/followers',
@ -51,6 +56,13 @@ serverFollowsRouter.get('/followers',
asyncMiddleware(listFollowers)
)
serverFollowsRouter.delete('/followers/:nameWithHost',
authenticate,
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
asyncMiddleware(removeFollowerValidator),
asyncMiddleware(removeFollower)
)
// ---------------------------------------------------------------------------
export {
@ -103,7 +115,7 @@ async function followInstance (req: express.Request, res: express.Response) {
return res.status(204).end()
}
async function removeFollow (req: express.Request, res: express.Response) {
async function removeFollowing (req: express.Request, res: express.Response) {
const follow = res.locals.follow
await sequelizeTypescript.transaction(async t => {
@ -123,3 +135,13 @@ async function removeFollow (req: express.Request, res: express.Response) {
return res.status(204).end()
}
async function removeFollower (req: express.Request, res: express.Response) {
const follow = res.locals.follow
await sendReject(follow)
await follow.destroy()
return res.status(204).end()
}

View File

@ -1,8 +1,10 @@
export * from './send-accept'
export * from './send-accept'
export * from './send-announce'
export * from './send-create'
export * from './send-delete'
export * from './send-follow'
export * from './send-like'
export * from './send-reject'
export * from './send-undo'
export * from './send-update'

View File

@ -0,0 +1,44 @@
import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
import { unicastTo } from './utils'
import { buildFollowActivity } from './send-follow'
import { logger } from '../../../helpers/logger'
async function sendReject (actorFollow: ActorFollowModel) {
const follower = actorFollow.ActorFollower
const me = actorFollow.ActorFollowing
if (!follower.serverId) { // This should never happen
logger.warn('Do not sending reject to local follower.')
return
}
logger.info('Creating job to reject follower %s.', follower.url)
const followUrl = getActorFollowActivityPubUrl(actorFollow)
const followData = buildFollowActivity(followUrl, follower, me)
const url = getActorFollowAcceptActivityPubUrl(actorFollow)
const data = buildRejectActivity(url, me, followData)
return unicastTo(data, me, follower.inboxUrl)
}
// ---------------------------------------------------------------------------
export {
sendReject
}
// ---------------------------------------------------------------------------
function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject {
return {
type: 'Reject',
id: url,
actor: byActor.url,
object: followActivityData
}
}

View File

@ -1,6 +1,6 @@
import { VideoModel } from '../models/video/video'
import { basename, dirname, join } from 'path'
import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY, sequelizeTypescript } from '../initializers'
import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, sequelizeTypescript } from '../initializers'
import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
import { getVideoFileSize } from '../helpers/ffmpeg-utils'
import { sha256 } from '../helpers/core-utils'
@ -20,6 +20,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t)
playlist.p2pMediaLoaderInfohashes = await VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlist.playlistUrl, videoFiles)
playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
await playlist.save({ transaction: t })
})
}

View File

@ -7,6 +7,10 @@ import { getServerActor } from '../../helpers/utils'
import { CONFIG, SERVER_ACTOR_NAME } from '../../initializers'
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
import { areValidationErrors } from './utils'
import { ActorModel } from '../../models/activitypub/actor'
import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
import { getOrCreateActorAndServerAndModel } from '../../lib/activitypub'
import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
const followValidator = [
body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
@ -33,7 +37,7 @@ const removeFollowingValidator = [
param('host').custom(isHostValid).withMessage('Should have a valid host'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking unfollow parameters', { parameters: req.params })
logger.debug('Checking unfollowing parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
@ -44,7 +48,36 @@ const removeFollowingValidator = [
return res
.status(404)
.json({
error: `Follower ${req.params.host} not found.`
error: `Following ${req.params.host} not found.`
})
.end()
}
res.locals.follow = follow
return next()
}
]
const removeFollowerValidator = [
param('nameWithHost').custom(isValidActorHandle).withMessage('Should have a valid nameWithHost'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking remove follower parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
const serverActor = await getServerActor()
const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost)
const actor = await ActorModel.loadByUrl(actorUrl)
const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, serverActor.id)
if (!follow) {
return res
.status(404)
.json({
error: `Follower ${req.params.nameWithHost} not found.`
})
.end()
}
@ -58,5 +91,6 @@ const removeFollowingValidator = [
export {
followValidator,
removeFollowingValidator
removeFollowingValidator,
removeFollowerValidator
}

View File

@ -0,0 +1,78 @@
/* tslint:disable:no-unused-expression */
import * as chai from 'chai'
import 'mocha'
import { flushAndRunMultipleServers, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index'
import {
follow,
getFollowersListPaginationAndSort,
getFollowingListPaginationAndSort,
removeFollower
} from '../../../../shared/utils/server/follows'
import { waitJobs } from '../../../../shared/utils/server/jobs'
import { ActorFollow } from '../../../../shared/models/actors'
const expect = chai.expect
describe('Test follows moderation', function () {
let servers: ServerInfo[] = []
before(async function () {
this.timeout(30000)
servers = await flushAndRunMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
})
it('Should have server 1 following server 2', async function () {
this.timeout(30000)
await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken)
await waitJobs(servers)
})
it('Should have correct follows', async function () {
{
const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
expect(res.body.total).to.equal(1)
const follow = res.body.data[0] as ActorFollow
expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
}
{
const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt')
expect(res.body.total).to.equal(1)
const follow = res.body.data[0] as ActorFollow
expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
}
})
it('Should remove follower on server 2', async function () {
await removeFollower(servers[1].url, servers[1].accessToken, servers[0])
await waitJobs(servers)
})
it('Should not not have follows anymore', async function () {
{
const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt')
expect(res.body.total).to.equal(0)
}
{
const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt')
expect(res.body.total).to.equal(0)
}
})
after(async function () {
killallServers(servers)
})
})

View File

@ -3,6 +3,7 @@ import './contact-form'
import './email'
import './follow-constraints'
import './follows'
import './follows-moderation'
import './handle-down'
import './jobs'
import './reverse-proxy'

View File

@ -47,13 +47,21 @@ async function follow (follower: string, following: string[], accessToken: strin
async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
const path = '/api/v1/server/following/' + target.host
const res = await request(url)
return request(url)
.delete(path)
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.expect(expectedStatus)
}
return res
function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = 204) {
const path = '/api/v1/server/followers/peertube@' + follower.host
return request(url)
.delete(path)
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.expect(expectedStatus)
}
async function doubleFollow (server1: ServerInfo, server2: ServerInfo) {
@ -74,6 +82,7 @@ export {
getFollowersListPaginationAndSort,
getFollowingListPaginationAndSort,
unfollow,
removeFollower,
follow,
doubleFollow
}