mirror of https://github.com/Chocobozzz/PeerTube
Fix federation with some actors
That don't have a shared inbox, or a URLpull/2154/head
parent
a0e6d26759
commit
47581df073
|
@ -6,7 +6,6 @@ import { processActivities } from '../../lib/activitypub/process/process'
|
|||
import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares'
|
||||
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
|
||||
import { queue } from 'async'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { MActorDefault, MActorSignature } from '../../typings/models'
|
||||
|
||||
const inboxRouter = express.Router()
|
||||
|
|
|
@ -6,7 +6,12 @@ import { isHostValid } from '../servers'
|
|||
import { peertubeTruncate } from '@server/helpers/core-utils'
|
||||
|
||||
function isActorEndpointsObjectValid (endpointObject: any) {
|
||||
return isActivityPubUrlValid(endpointObject.sharedInbox)
|
||||
if (endpointObject && endpointObject.sharedInbox) {
|
||||
return isActivityPubUrlValid(endpointObject.sharedInbox)
|
||||
}
|
||||
|
||||
// Shared inbox is optional
|
||||
return true
|
||||
}
|
||||
|
||||
function isActorPublicKeyObjectValid (publicKeyObject: any) {
|
||||
|
@ -16,7 +21,7 @@ function isActorPublicKeyObjectValid (publicKeyObject: any) {
|
|||
}
|
||||
|
||||
function isActorTypeValid (type: string) {
|
||||
return type === 'Person' || type === 'Application' || type === 'Group'
|
||||
return type === 'Person' || type === 'Application' || type === 'Group' || type === 'Service' || type === 'Organization'
|
||||
}
|
||||
|
||||
function isActorPublicKeyValid (publicKey: string) {
|
||||
|
@ -81,9 +86,11 @@ function sanitizeAndCheckActorObject (object: any) {
|
|||
}
|
||||
|
||||
function normalizeActor (actor: any) {
|
||||
if (!actor || !actor.url) return
|
||||
if (!actor) return
|
||||
|
||||
if (typeof actor.url !== 'string') {
|
||||
if (!actor.url) {
|
||||
actor.url = actor.id
|
||||
} else if (typeof actor.url !== 'string') {
|
||||
actor.url = actor.url.href || actor.url.url
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 440
|
||||
const LAST_MIGRATION_VERSION = 445
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -459,7 +459,9 @@ const ACTIVITY_PUB = {
|
|||
const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
|
||||
GROUP: 'Group',
|
||||
PERSON: 'Person',
|
||||
APPLICATION: 'Application'
|
||||
APPLICATION: 'Application',
|
||||
ORGANIZATION: 'Organization',
|
||||
SERVICE: 'Service'
|
||||
}
|
||||
|
||||
const HTTP_SIGNATURE = {
|
||||
|
|
|
@ -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<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('actor', 'sharedInboxUrl', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -163,9 +163,12 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
|
|||
actorInstance.followingCount = followingCount
|
||||
actorInstance.inboxUrl = attributes.inbox
|
||||
actorInstance.outboxUrl = attributes.outbox
|
||||
actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox
|
||||
actorInstance.followersUrl = attributes.followers
|
||||
actorInstance.followingUrl = attributes.following
|
||||
|
||||
if (attributes.endpoints && attributes.endpoints.sharedInbox) {
|
||||
actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox
|
||||
}
|
||||
}
|
||||
|
||||
type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
|
||||
|
@ -437,9 +440,12 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
|
|||
followingCount: followingCount,
|
||||
inboxUrl: actorJSON.inbox,
|
||||
outboxUrl: actorJSON.outbox,
|
||||
sharedInboxUrl: actorJSON.endpoints.sharedInbox,
|
||||
followersUrl: actorJSON.followers,
|
||||
followingUrl: actorJSON.following
|
||||
followingUrl: actorJSON.following,
|
||||
|
||||
sharedInboxUrl: actorJSON.endpoints && actorJSON.endpoints.sharedInbox
|
||||
? actorJSON.endpoints.sharedInbox
|
||||
: null,
|
||||
})
|
||||
|
||||
const avatarInfo = await getAvatarInfoIfExists(actorJSON)
|
||||
|
|
|
@ -100,7 +100,7 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti
|
|||
if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException)
|
||||
|
||||
// Send to origin
|
||||
t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
|
||||
t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox()))
|
||||
}
|
||||
|
||||
function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
|
||||
|
|
|
@ -71,7 +71,7 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t:
|
|||
if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException)
|
||||
|
||||
// Send to origin
|
||||
t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
|
||||
t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox()))
|
||||
}
|
||||
|
||||
async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
|
||||
|
|
|
@ -18,7 +18,7 @@ async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, vi
|
|||
const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
|
||||
const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience)
|
||||
|
||||
t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
|
||||
t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()))
|
||||
}
|
||||
|
||||
function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { JobQueue } from '../../job-queue'
|
|||
import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
|
||||
import { getServerActor } from '../../../helpers/utils'
|
||||
import { afterCommitIfTransaction } from '../../../helpers/database-utils'
|
||||
import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
|
||||
import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
|
||||
|
||||
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
|
||||
byActor: MActorLight,
|
||||
|
@ -24,7 +24,7 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
|
|||
const activity = activityBuilder(audience)
|
||||
|
||||
return afterCommitIfTransaction(transaction, () => {
|
||||
return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
|
||||
return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
|
|||
async function forwardVideoRelatedActivity (
|
||||
activity: Activity,
|
||||
t: Transaction,
|
||||
followersException: MActorFollowerException[] = [],
|
||||
followersException: MActorWithInboxes[] = [],
|
||||
video: MVideo
|
||||
) {
|
||||
// Mastodon does not add our announces in audience, so we forward to them manually
|
||||
|
@ -53,7 +53,7 @@ async function forwardVideoRelatedActivity (
|
|||
async function forwardActivity (
|
||||
activity: Activity,
|
||||
t: Transaction,
|
||||
followersException: MActorFollowerException[] = [],
|
||||
followersException: MActorWithInboxes[] = [],
|
||||
additionalFollowerUrls: string[] = []
|
||||
) {
|
||||
logger.info('Forwarding activity %s.', activity.id)
|
||||
|
@ -90,7 +90,7 @@ async function broadcastToFollowers (
|
|||
byActor: MActorId,
|
||||
toFollowersOf: MActorId[],
|
||||
t: Transaction,
|
||||
actorsException: MActorFollowerException[] = []
|
||||
actorsException: MActorWithInboxes[] = []
|
||||
) {
|
||||
const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
|
||||
|
||||
|
@ -102,7 +102,7 @@ async function broadcastToActors (
|
|||
byActor: MActorId,
|
||||
toActors: MActor[],
|
||||
t?: Transaction,
|
||||
actorsException: MActorFollowerException[] = []
|
||||
actorsException: MActorWithInboxes[] = []
|
||||
) {
|
||||
const uris = await computeUris(toActors, actorsException)
|
||||
return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
|
||||
|
@ -147,7 +147,7 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
|
||||
async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
|
||||
const toActorFollowerIds = toFollowersOf.map(a => a.id)
|
||||
|
||||
const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
|
||||
|
@ -156,11 +156,11 @@ async function computeFollowerUris (toFollowersOf: MActorId[], actorsException:
|
|||
return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
|
||||
}
|
||||
|
||||
async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
|
||||
async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) {
|
||||
const serverActor = await getServerActor()
|
||||
const targetUrls = toActors
|
||||
.filter(a => a.id !== serverActor.id) // Don't send to ourselves
|
||||
.map(a => a.sharedInboxUrl || a.inboxUrl)
|
||||
.map(a => a.getSharedInbox())
|
||||
|
||||
const toActorSharedInboxesSet = new Set(targetUrls)
|
||||
|
||||
|
@ -169,10 +169,10 @@ async function computeUris (toActors: MActor[], actorsException: MActorFollowerE
|
|||
.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
|
||||
}
|
||||
|
||||
async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
|
||||
async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
|
||||
const serverActor = await getServerActor()
|
||||
|
||||
return actorsException
|
||||
.map(f => f.sharedInboxUrl || f.inboxUrl)
|
||||
.map(f => f.getSharedInbox())
|
||||
.concat([ serverActor.sharedInboxUrl ])
|
||||
}
|
||||
|
|
|
@ -574,8 +574,8 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
|
|||
}
|
||||
|
||||
const selections: string[] = []
|
||||
if (distinct === true) selections.push('DISTINCT("Follows"."' + columnUrl + '") AS "url"')
|
||||
else selections.push('"Follows"."' + columnUrl + '" AS "url"')
|
||||
if (distinct === true) selections.push('DISTINCT("Follows"."' + columnUrl + '") AS "selectionUrl"')
|
||||
else selections.push('"Follows"."' + columnUrl + '" AS "selectionUrl"')
|
||||
|
||||
selections.push('COUNT(*) AS "total"')
|
||||
|
||||
|
@ -585,7 +585,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
|
|||
let query = 'SELECT ' + selection + ' FROM "actor" ' +
|
||||
'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
|
||||
'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
|
||||
'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' '
|
||||
'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' AND "selectionUrl" IS NOT NULL '
|
||||
|
||||
if (count !== undefined) query += 'LIMIT ' + count
|
||||
if (start !== undefined) query += ' OFFSET ' + start
|
||||
|
@ -599,7 +599,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
|
|||
}
|
||||
|
||||
const [ followers, [ dataTotal ] ] = await Promise.all(tasks)
|
||||
const urls: string[] = followers.map(f => f.url)
|
||||
const urls: string[] = followers.map(f => f.selectionUrl)
|
||||
|
||||
return {
|
||||
data: urls,
|
||||
|
|
|
@ -44,7 +44,8 @@ import {
|
|||
MActorFull,
|
||||
MActorHost,
|
||||
MActorServer,
|
||||
MActorSummaryFormattable
|
||||
MActorSummaryFormattable,
|
||||
MActorWithInboxes
|
||||
} from '../../typings/models'
|
||||
import * as Bluebird from 'bluebird'
|
||||
|
||||
|
@ -179,8 +180,8 @@ export class ActorModel extends Model<ActorModel> {
|
|||
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
|
||||
outboxUrl: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
|
||||
@AllowNull(true)
|
||||
@Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url', true))
|
||||
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
|
||||
sharedInboxUrl: string
|
||||
|
||||
|
@ -402,6 +403,10 @@ export class ActorModel extends Model<ActorModel> {
|
|||
})
|
||||
}
|
||||
|
||||
getSharedInbox (this: MActorWithInboxes) {
|
||||
return this.sharedInboxUrl || this.inboxUrl
|
||||
}
|
||||
|
||||
toFormattedSummaryJSON (this: MActorSummaryFormattable) {
|
||||
let avatar: Avatar = null
|
||||
if (this.Avatar) {
|
||||
|
|
|
@ -19,7 +19,7 @@ export type MActorUsername = Pick<MActor, 'preferredUsername'>
|
|||
|
||||
export type MActorFollowersUrl = Pick<MActor, 'followersUrl'>
|
||||
export type MActorAudience = MActorUrl & MActorFollowersUrl
|
||||
export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
|
||||
export type MActorWithInboxes = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl' | 'getSharedInbox'>
|
||||
export type MActorSignature = MActorAccountChannelId
|
||||
|
||||
export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ActivityPubAttributedTo } from './objects/common-objects'
|
||||
|
||||
export type ActivityPubActorType = 'Person' | 'Application' | 'Group'
|
||||
export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization'
|
||||
|
||||
export interface ActivityPubActor {
|
||||
'@context': any[]
|
||||
|
|
Loading…
Reference in New Issue