1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-05 02:39:33 +02:00

Support raw strings for AP to/cc

This commit is contained in:
Chocobozzz 2025-05-09 11:36:21 +02:00
parent 5138e28ff8
commit 8d3bc24c4b
No known key found for this signature in database
GPG key ID: 583A612D890159BE
9 changed files with 76 additions and 66 deletions

View file

@ -11,6 +11,7 @@ export function findCommonElement <T> (array1: T[], array2: T[]) {
// Avoid conflict with other toArray() functions // Avoid conflict with other toArray() functions
export function arrayify<T> (element: T | T[]) { export function arrayify<T> (element: T | T[]) {
if (Array.isArray(element)) return element if (Array.isArray(element)) return element
if (element === undefined || element === null) return []
return [ element ] return [ element ]
} }
@ -35,9 +36,9 @@ export function shuffle <T> (elements: T[]) {
const shuffled = [ ...elements ] const shuffled = [ ...elements ]
for (let i = shuffled.length - 1; i > 0; i--) { for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); const j = Math.floor(Math.random() * (i + 1))
[ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ] ;[ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ]
} }
return shuffled return shuffled

View file

@ -12,46 +12,53 @@ import {
} from './objects/index.js' } from './objects/index.js'
export type ActivityUpdateObject = export type ActivityUpdateObject =
Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string> | ActivityPubActor | Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string>
| ActivityPubActor
// Cannot Extract from Activity because of circular reference // Cannot Extract from Activity because of circular reference
export type ActivityUndoObject = export type ActivityUndoObject =
ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate<CacheFileObject | string> | ActivityAnnounce | ActivityFollow
| ActivityLike
| ActivityDislike
| ActivityCreate<CacheFileObject | string>
| ActivityAnnounce
export type ActivityCreateObject = export type ActivityCreateObject = Extract<
Extract<ActivityObject, VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string> ActivityObject,
VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string
>
export type Activity = export type Activity =
ActivityCreate<ActivityCreateObject> | | ActivityCreate<ActivityCreateObject>
ActivityUpdate<ActivityUpdateObject> | | ActivityUpdate<ActivityUpdateObject>
ActivityDelete | | ActivityDelete
ActivityFollow | | ActivityFollow
ActivityAccept | | ActivityAccept
ActivityAnnounce | | ActivityAnnounce
ActivityUndo<ActivityUndoObject> | | ActivityUndo<ActivityUndoObject>
ActivityLike | | ActivityLike
ActivityReject | | ActivityReject
ActivityView | | ActivityView
ActivityDislike | | ActivityDislike
ActivityFlag | | ActivityFlag
ActivityApproveReply | | ActivityApproveReply
ActivityRejectReply | ActivityRejectReply
export type ActivityType = export type ActivityType =
'Create' | | 'Create'
'Update' | | 'Update'
'Delete' | | 'Delete'
'Follow' | | 'Follow'
'Accept' | | 'Accept'
'Announce' | | 'Announce'
'Undo' | | 'Undo'
'Like' | | 'Like'
'Reject' | | 'Reject'
'View' | | 'View'
'Dislike' | | 'Dislike'
'Flag' | | 'Flag'
'ApproveReply' | | 'ApproveReply'
'RejectReply' | 'RejectReply'
export interface ActivityAudience { export interface ActivityAudience {
to: string[] to: string[]
@ -61,8 +68,8 @@ export interface ActivityAudience {
export interface BaseActivity { export interface BaseActivity {
'@context'?: any[] '@context'?: any[]
id: string id: string
to?: string[] to?: string[] | string
cc?: string[] cc?: string[] | string
actor: string | ActivityPubActor actor: string | ActivityPubActor
type: ActivityType type: ActivityType
signature?: ActivityPubSignature signature?: ActivityPubSignature

View file

@ -1,3 +1,4 @@
import { arrayify } from '@peertube/peertube-core-utils'
import { ContextType } from '@peertube/peertube-models' import { ContextType } from '@peertube/peertube-models'
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js' import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
import { isArray } from './custom-validators/misc.js' import { isArray } from './custom-validators/misc.js'
@ -53,12 +54,10 @@ export function getAPPublicValue (): 'https://www.w3.org/ns/activitystreams#Publ
return 'https://www.w3.org/ns/activitystreams#Public' return 'https://www.w3.org/ns/activitystreams#Public'
} }
export function hasAPPublic (toOrCC: string[]) { export function hasAPPublic (toOrCC: string[] | string) {
if (!isArray(toOrCC)) return false
const publicValue = getAPPublicValue() const publicValue = getAPPublicValue()
return toOrCC.some(f => f === 'as:Public' || publicValue) return arrayify(toOrCC).some(f => f === 'as:Public' || publicValue)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -35,6 +35,7 @@ export function isBaseActivityValid (activity: any, type: string) {
export function isUrlCollectionValid (collection: any) { export function isUrlCollectionValid (collection: any) {
return collection === undefined || return collection === undefined ||
(typeof collection === 'string' && isActivityPubUrlValid(collection)) ||
(Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t))) (Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t)))
} }

View file

@ -1,8 +1,8 @@
import { ActivityTombstoneObject, VideoCommentObject } from '@peertube/peertube-models'
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js' import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
import validator from 'validator' import validator from 'validator'
import { exists, isArray, isDateValid } from '../misc.js' import { exists, isDateValid } from '../misc.js'
import { isActivityPubUrlValid } from './misc.js' import { isActivityPubUrlValid } from './misc.js'
import { ActivityTombstoneObject, VideoCommentObject } from '@peertube/peertube-models'
function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | ActivityTombstoneObject) { function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | ActivityTombstoneObject) {
if (!comment) return false if (!comment) return false
@ -23,7 +23,6 @@ function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | Activ
isActivityPubUrlValid(comment.inReplyTo) && isActivityPubUrlValid(comment.inReplyTo) &&
isDateValid(comment.published) && isDateValid(comment.published) &&
isActivityPubUrlValid(comment.url) && isActivityPubUrlValid(comment.url) &&
isArray(comment.to) &&
(!exists(comment.replyApproval) || isActivityPubUrlValid(comment.replyApproval)) && (!exists(comment.replyApproval) || isActivityPubUrlValid(comment.replyApproval)) &&
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments (hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
} }

View file

@ -1,9 +1,9 @@
import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor.js'
import { logger } from '@server/helpers/logger.js'
import { ActivityPubActor, ActivityPubOrderedCollection } from '@peertube/peertube-models' import { ActivityPubActor, ActivityPubOrderedCollection } from '@peertube/peertube-models'
import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor.js'
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
import { logger } from '@server/helpers/logger.js'
import { fetchAP } from '../../activity.js' import { fetchAP } from '../../activity.js'
import { checkUrlsSameHost } from '../../url.js' import { checkUrlsSameHost } from '../../url.js'
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
export async function fetchRemoteActor ( export async function fetchRemoteActor (
actorUrl: string, actorUrl: string,
@ -17,7 +17,7 @@ export async function fetchRemoteActor (
logger.debug('Remote actor JSON is not valid.', { actorJSON: body }) logger.debug('Remote actor JSON is not valid.', { actorJSON: body })
// Retry with the public key owner // Retry with the public key owner
if (canRefetchPublicKeyOwner && hasPublicKeyOwner(body)) { if (canRefetchPublicKeyOwner && hasPublicKeyOwner(actorUrl, body)) {
logger.debug('Retrying with public key owner ' + body.publicKey.owner) logger.debug('Retrying with public key owner ' + body.publicKey.owner)
return fetchRemoteActor(body.publicKey.owner, false) return fetchRemoteActor(body.publicKey.owner, false)
@ -63,6 +63,6 @@ async function fetchActorTotalItems (url: string) {
} }
} }
function hasPublicKeyOwner (actor: ActivityPubActor) { function hasPublicKeyOwner (actorUrl: string, actor: ActivityPubActor) {
return isUrlValid(actor?.publicKey?.owner) return isUrlValid(actor?.publicKey?.owner) && checkUrlsSameHost(actorUrl, actor.publicKey.owner)
} }

View file

@ -27,6 +27,7 @@ import { sendReplyApproval } from '../send/send-reply-approval.js'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js' import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
import { resolveThread } from '../video-comments.js' import { resolveThread } from '../video-comments.js'
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js' import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
import { arrayify } from '@peertube/peertube-core-utils'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) { async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
const { activity, byActor } = options const { activity, byActor } = options
@ -186,5 +187,5 @@ async function processCreatePlaylist (
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url)
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: activity.to }) await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: arrayify(activity.to) })
} }

View file

@ -1,3 +1,4 @@
import { arrayify } from '@peertube/peertube-core-utils'
import { import {
ActivityPubActor, ActivityPubActor,
ActivityPubActorType, ActivityPubActorType,
@ -78,7 +79,7 @@ async function processUpdateVideo (activity: ActivityUpdate<VideoObject | string
if (created) return if (created) return
const updater = new APVideoUpdater(videoObject, video) const updater = new APVideoUpdater(videoObject, video)
return updater.update(activity.to) return updater.update(arrayify(activity.to))
} }
async function processUpdateCacheFile ( async function processUpdateCacheFile (
@ -127,5 +128,5 @@ async function processUpdatePlaylist (
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url)
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: activity.to }) await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: arrayify(activity.to) })
} }

View file

@ -17,6 +17,7 @@ import {
} from '../../../../types/models/index.js' } from '../../../../types/models/index.js'
import { JobQueue } from '../../../job-queue/index.js' import { JobQueue } from '../../../job-queue/index.js'
import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAudience } from './audience-utils.js' import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAudience } from './audience-utils.js'
import { arrayify } from '@peertube/peertube-core-utils'
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
byActor: MActorLight byActor: MActorLight
@ -102,8 +103,8 @@ async function forwardActivity (
) { ) {
logger.info('Forwarding activity %s.', activity.id) logger.info('Forwarding activity %s.', activity.id)
const to = activity.to || [] const to = arrayify(activity.to)
const cc = activity.cc || [] const cc = arrayify(activity.cc)
const followersUrls = additionalFollowerUrls const followersUrls = additionalFollowerUrls
for (const dest of to.concat(cc)) { for (const dest of to.concat(cc)) {