1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-05 19:42:24 +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

@ -1,4 +1,4 @@
export function findCommonElement <T> (array1: T[], array2: T[]) { export function findCommonElement<T> (array1: T[], array2: T[]) {
for (const a of array1) { for (const a of array1) {
for (const b of array2) { for (const b of array2) {
if (a === b) return a if (a === b) return a
@ -9,13 +9,14 @@ 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 ]
} }
export function unarray <T> (element: T | T[]) { export function unarray<T> (element: T | T[]) {
if (Array.isArray(element)) { if (Array.isArray(element)) {
if (element.length === 0) return undefined if (element.length === 0) return undefined
@ -26,24 +27,24 @@ export function unarray <T> (element: T | T[]) {
} }
// Avoid conflict with other uniq() functions // Avoid conflict with other uniq() functions
export function uniqify <T> (elements: T[]) { export function uniqify<T> (elements: T[]) {
return Array.from(new Set(elements)) return Array.from(new Set(elements))
} }
// Thanks: https://stackoverflow.com/a/12646864 // Thanks: https://stackoverflow.com/a/12646864
export function shuffle <T> (elements: T[]) { 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
} }
export function sortBy <T> (obj: T[], key1: string, key2?: string): T[] { export function sortBy<T> (obj: T[], key1: string, key2?: string): T[] {
return obj.sort((a, b) => { return obj.sort((a, b) => {
const elem1 = key2 ? a[key1][key2] : a[key1] const elem1 = key2 ? a[key1][key2] : a[key1]
const elem2 = key2 ? b[key1][key2] : b[key1] const elem2 = key2 ? b[key1][key2] : b[key1]
@ -54,7 +55,7 @@ export function sortBy <T> (obj: T[], key1: string, key2?: string): T[] {
}) })
} }
export function maxBy <T> (arr: T[], property: keyof T) { export function maxBy<T> (arr: T[], property: keyof T) {
let result: T let result: T
for (const obj of arr) { for (const obj of arr) {
@ -64,7 +65,7 @@ export function maxBy <T> (arr: T[], property: keyof T) {
return result return result
} }
export function minBy <T> (arr: T[], property: keyof T) { export function minBy<T> (arr: T[], property: keyof T) {
let result: T let result: T
for (const obj of arr) { for (const obj of arr) {

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,19 +68,19 @@ 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
} }
export interface ActivityCreate <T extends ActivityCreateObject> extends BaseActivity { export interface ActivityCreate<T extends ActivityCreateObject> extends BaseActivity {
type: 'Create' type: 'Create'
object: T object: T
} }
export interface ActivityUpdate <T extends ActivityUpdateObject> extends BaseActivity { export interface ActivityUpdate<T extends ActivityUpdateObject> extends BaseActivity {
type: 'Update' type: 'Update'
object: T object: T
} }
@ -115,7 +122,7 @@ export interface ActivityAnnounce extends BaseActivity {
object: APObjectId object: APObjectId
} }
export interface ActivityUndo <T extends ActivityUndoObject> extends BaseActivity { export interface ActivityUndo<T extends ActivityUndoObject> extends BaseActivity {
type: 'Undo' type: 'Undo'
object: T object: T
} }

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)) {
@ -285,7 +286,7 @@ async function computeUris (toActors: MActor[], actorsException: MActorWithInbox
const sharedInboxesException = await buildSharedInboxesException(actorsException) const sharedInboxesException = await buildSharedInboxesException(actorsException)
return Array.from(toActorSharedInboxesSet) return Array.from(toActorSharedInboxesSet)
.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false) .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
} }
async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) { async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {