diff options
-rw-r--r-- | server/helpers/custom-validators/activitypub/actor.ts | 15 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0425-nullable-actor-fields.ts | 26 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-flag.ts | 40 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 12 | ||||
-rw-r--r-- | shared/models/activitypub/activity.ts | 2 | ||||
-rw-r--r-- | shared/models/activitypub/objects/video-abuse-object.ts | 2 |
7 files changed, 67 insertions, 32 deletions
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) { | |||
27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | 27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) |
28 | } | 28 | } |
29 | 29 | ||
30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' | 30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' |
31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) | 31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) |
32 | function isActorPreferredUsernameValid (preferredUsername: string) { | 32 | function isActorPreferredUsernameValid (preferredUsername: string) { |
33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) | 33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) |
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { | |||
46 | return exists(actor) && | 46 | return exists(actor) && |
47 | isActivityPubUrlValid(actor.id) && | 47 | isActivityPubUrlValid(actor.id) && |
48 | isActorTypeValid(actor.type) && | 48 | isActorTypeValid(actor.type) && |
49 | isActivityPubUrlValid(actor.following) && | ||
50 | isActivityPubUrlValid(actor.followers) && | ||
51 | isActivityPubUrlValid(actor.inbox) && | 49 | isActivityPubUrlValid(actor.inbox) && |
52 | isActivityPubUrlValid(actor.outbox) && | ||
53 | isActorPreferredUsernameValid(actor.preferredUsername) && | 50 | isActorPreferredUsernameValid(actor.preferredUsername) && |
54 | isActivityPubUrlValid(actor.url) && | 51 | isActivityPubUrlValid(actor.url) && |
55 | isActorPublicKeyObjectValid(actor.publicKey) && | 52 | isActorPublicKeyObjectValid(actor.publicKey) && |
56 | isActorEndpointsObjectValid(actor.endpoints) && | 53 | isActorEndpointsObjectValid(actor.endpoints) && |
57 | setValidAttributedTo(actor) && | ||
58 | 54 | ||
59 | // If this is not an account, it should be attributed to an account | 55 | (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && |
56 | (!actor.following || isActivityPubUrlValid(actor.following)) && | ||
57 | (!actor.followers || isActivityPubUrlValid(actor.followers)) && | ||
58 | |||
59 | setValidAttributedTo(actor) && | ||
60 | // If this is a group (a channel), it should be attributed to an account | ||
60 | // In PeerTube we use this to attach a video channel to a specific account | 61 | // In PeerTube we use this to attach a video channel to a specific account |
61 | (actor.type === 'Person' || actor.attributedTo.length !== 0) | 62 | (actor.type !== 'Group' || actor.attributedTo.length !== 0) |
62 | } | 63 | } |
63 | 64 | ||
64 | function isActorFollowingCountValid (value: string) { | 65 | 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' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 420 | 17 | const LAST_MIGRATION_VERSION = 425 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING, | ||
11 | allowNull: true | ||
12 | } | ||
13 | |||
14 | await utils.queryInterface.changeColumn('actor', 'outboxUrl', data) | ||
15 | await utils.queryInterface.changeColumn('actor', 'followersUrl', data) | ||
16 | await utils.queryInterface.changeColumn('actor', 'followingUrl', data) | ||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
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 { | |||
26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { | 26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { |
27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) | 27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) |
28 | 28 | ||
29 | logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object)) | ||
30 | |||
31 | const account = byActor.Account | 29 | const account = byActor.Account |
32 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) | 30 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) |
33 | 31 | ||
34 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) | 32 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] |
35 | 33 | ||
36 | const videoAbuse = await sequelizeTypescript.transaction(async t => { | 34 | for (const object of objects) { |
37 | const videoAbuseData = { | 35 | try { |
38 | reporterAccountId: account.id, | 36 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) |
39 | reason: flag.content, | 37 | |
40 | videoId: video.id, | 38 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) |
41 | state: VideoAbuseState.PENDING | ||
42 | } | ||
43 | 39 | ||
44 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo | 40 | const videoAbuse = await sequelizeTypescript.transaction(async t => { |
45 | videoAbuseInstance.Video = video | 41 | const videoAbuseData = { |
42 | reporterAccountId: account.id, | ||
43 | reason: flag.content, | ||
44 | videoId: video.id, | ||
45 | state: VideoAbuseState.PENDING | ||
46 | } | ||
46 | 47 | ||
47 | logger.info('Remote abuse for video uuid %s created', flag.object) | 48 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo |
49 | videoAbuseInstance.Video = video | ||
48 | 50 | ||
49 | return videoAbuseInstance | 51 | logger.info('Remote abuse for video uuid %s created', flag.object) |
50 | }) | ||
51 | 52 | ||
52 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | 53 | return videoAbuseInstance |
54 | }) | ||
55 | |||
56 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | ||
57 | } catch (err) { | ||
58 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | ||
59 | } | ||
60 | } | ||
53 | } | 61 | } |
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<ActorModel> { | |||
175 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 175 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
176 | inboxUrl: string | 176 | inboxUrl: string |
177 | 177 | ||
178 | @AllowNull(false) | 178 | @AllowNull(true) |
179 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) | 179 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) |
180 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 180 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
181 | outboxUrl: string | 181 | outboxUrl: string |
182 | 182 | ||
@@ -185,13 +185,13 @@ export class ActorModel extends Model<ActorModel> { | |||
185 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 185 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
186 | sharedInboxUrl: string | 186 | sharedInboxUrl: string |
187 | 187 | ||
188 | @AllowNull(false) | 188 | @AllowNull(true) |
189 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) | 189 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) |
190 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 190 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
191 | followersUrl: string | 191 | followersUrl: string |
192 | 192 | ||
193 | @AllowNull(false) | 193 | @AllowNull(true) |
194 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) | 194 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) |
195 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 195 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
196 | followingUrl: string | 196 | followingUrl: string |
197 | 197 | ||
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 { | |||
91 | export interface ActivityFlag extends BaseActivity { | 91 | export interface ActivityFlag extends BaseActivity { |
92 | type: 'Flag', | 92 | type: 'Flag', |
93 | content: string, | 93 | content: string, |
94 | object: APObject | 94 | object: APObject | APObject[] |
95 | } | 95 | } |
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 @@ | |||
1 | export interface VideoAbuseObject { | 1 | export interface VideoAbuseObject { |
2 | type: 'Flag', | 2 | type: 'Flag', |
3 | content: string | 3 | content: string |
4 | object: string | 4 | object: string | string[] |
5 | } | 5 | } |