diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index deb331abb..55bc8cc96 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts @@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) { validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) } -const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' +const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) function isActorPreferredUsernameValid (preferredUsername: string) { return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) @@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { return exists(actor) && isActivityPubUrlValid(actor.id) && isActorTypeValid(actor.type) && - isActivityPubUrlValid(actor.following) && - isActivityPubUrlValid(actor.followers) && isActivityPubUrlValid(actor.inbox) && - isActivityPubUrlValid(actor.outbox) && isActorPreferredUsernameValid(actor.preferredUsername) && isActivityPubUrlValid(actor.url) && isActorPublicKeyObjectValid(actor.publicKey) && isActorEndpointsObjectValid(actor.endpoints) && - setValidAttributedTo(actor) && - // If this is not an account, it should be attributed to an account + (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && + (!actor.following || isActivityPubUrlValid(actor.following)) && + (!actor.followers || isActivityPubUrlValid(actor.followers)) && + + setValidAttributedTo(actor) && + // If this is a group (a channel), it should be attributed to an account // In PeerTube we use this to attach a video channel to a specific account - (actor.type === 'Person' || actor.attributedTo.length !== 0) + (actor.type !== 'Group' || actor.attributedTo.length !== 0) } function isActorFollowingCountValid (value: string) { diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3dc178b11..908231a88 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 420 +const LAST_MIGRATION_VERSION = 425 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts new file mode 100644 index 000000000..4e5f9e6ab --- /dev/null +++ b/server/initializers/migrations/0425-nullable-actor-fields.ts @@ -0,0 +1,26 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize, + db: any +}): Promise { + const data = { + type: Sequelize.STRING, + allowNull: true + } + + await utils.queryInterface.changeColumn('actor', 'outboxUrl', data) + await utils.queryInterface.changeColumn('actor', 'followersUrl', data) + await utils.queryInterface.changeColumn('actor', 'followingUrl', data) +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 422386540..e6e9084de 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts @@ -26,28 +26,36 @@ export { async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) - logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object)) - const account = byActor.Account if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) - const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) + const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] - const videoAbuse = await sequelizeTypescript.transaction(async t => { - const videoAbuseData = { - reporterAccountId: account.id, - reason: flag.content, - videoId: video.id, - state: VideoAbuseState.PENDING + for (const object of objects) { + try { + logger.debug('Reporting remote abuse for video %s.', getAPId(object)) + + const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) + + const videoAbuse = await sequelizeTypescript.transaction(async t => { + const videoAbuseData = { + reporterAccountId: account.id, + reason: flag.content, + videoId: video.id, + state: VideoAbuseState.PENDING + } + + const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo + videoAbuseInstance.Video = video + + logger.info('Remote abuse for video uuid %s created', flag.object) + + return videoAbuseInstance + }) + + Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) + } catch (err) { + logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) } - - const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo - videoAbuseInstance.Video = video - - logger.info('Remote abuse for video uuid %s created', flag.object) - - return videoAbuseInstance - }) - - Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) + } } diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index fb4327e4f..67a1b5bc1 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -175,8 +175,8 @@ export class ActorModel extends Model { @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) inboxUrl: string - @AllowNull(false) - @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) + @AllowNull(true) + @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) outboxUrl: string @@ -185,13 +185,13 @@ export class ActorModel extends Model { @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) sharedInboxUrl: string - @AllowNull(false) - @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) + @AllowNull(true) + @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) followersUrl: string - @AllowNull(false) - @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) + @AllowNull(true) + @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) followingUrl: string diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 95801190d..492b672c7 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts @@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity { export interface ActivityFlag extends BaseActivity { type: 'Flag', content: string, - object: APObject + object: APObject | APObject[] } diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts index 40e7abd57..5f1264a76 100644 --- a/shared/models/activitypub/objects/video-abuse-object.ts +++ b/shared/models/activitypub/objects/video-abuse-object.ts @@ -1,5 +1,5 @@ export interface VideoAbuseObject { type: 'Flag', content: string - object: string + object: string | string[] }