summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authorDaiki Mizukami <tesaguriguma@gmail.com>2024-08-09 12:10:51 +0900
committerGitHub <noreply@github.com>2024-08-09 12:10:51 +0900
commit0d508db8a7a36218d38231af4e718aff0e94d9bc (patch)
treee17b48907ba006f0492e77084093015516ce4b42 /packages/backend/src/core
parentci: change prerelease channels to alpha, beta, and rc (#14376) (diff)
downloadmisskey-0d508db8a7a36218d38231af4e718aff0e94d9bc.tar.gz
misskey-0d508db8a7a36218d38231af4e718aff0e94d9bc.tar.bz2
misskey-0d508db8a7a36218d38231af4e718aff0e94d9bc.zip
fix(backend): check visibility of following/followers of remote users / feat: moderators can see following/followers of all users (#14375)
* fix(backend): check visibility of following/followers of remote users Resolves https://github.com/misskey-dev/misskey/issues/13362. * test(backend): add tests for visibility of following/followers of remote users * docs(changelog): update CHANGELOG.md * feat: moderators can see following/followers of all users * docs(changelog): update CHANGELOG.md * refactor(backend): minor refactoring `createPerson`と`if`の条件を統一するとともに、異常系の 処理をearly returnに追い出すための変更。 * feat(backend): moderators can see following/followers count of all users As per https://github.com/misskey-dev/misskey/pull/14375#issuecomment-2275044908.
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/activitypub/models/ApPersonService.ts50
-rw-r--r--packages/backend/src/core/activitypub/type.ts6
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts4
3 files changed, 55 insertions, 5 deletions
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 457205e023..f3ddf3952c 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -48,7 +48,7 @@ import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js';
-import type { IActor, IObject } from '../type.js';
+import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
const nameLength = 128;
const summaryLength = 2048;
@@ -296,6 +296,21 @@ export class ApPersonService implements OnModuleInit {
const isBot = getApType(object) === 'Service' || getApType(object) === 'Application';
+ const [followingVisibility, followersVisibility] = await Promise.all(
+ [
+ this.isPublicCollection(person.following, resolver),
+ this.isPublicCollection(person.followers, resolver),
+ ].map((p): Promise<'public' | 'private'> => p
+ .then(isPublic => isPublic ? 'public' : 'private')
+ .catch(err => {
+ if (!(err instanceof StatusError) || err.isRetryable) {
+ this.logger.error('error occurred while fetching following/followers collection', { stack: err });
+ }
+ return 'private';
+ })
+ )
+ );
+
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url);
@@ -357,6 +372,8 @@ export class ApPersonService implements OnModuleInit {
description: _description,
url,
fields,
+ followingVisibility,
+ followersVisibility,
birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null,
userHost: host,
@@ -464,6 +481,23 @@ export class ApPersonService implements OnModuleInit {
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
+ const [followingVisibility, followersVisibility] = await Promise.all(
+ [
+ this.isPublicCollection(person.following, resolver),
+ this.isPublicCollection(person.followers, resolver),
+ ].map((p): Promise<'public' | 'private' | undefined> => p
+ .then(isPublic => isPublic ? 'public' : 'private')
+ .catch(err => {
+ if (!(err instanceof StatusError) || err.isRetryable) {
+ this.logger.error('error occurred while fetching following/followers collection', { stack: err });
+ // Do not update the visibiility on transient errors.
+ return undefined;
+ }
+ return 'private';
+ })
+ )
+ );
+
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url);
@@ -532,6 +566,8 @@ export class ApPersonService implements OnModuleInit {
url,
fields,
description: _description,
+ followingVisibility,
+ followersVisibility,
birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null,
});
@@ -703,4 +739,16 @@ export class ApPersonService implements OnModuleInit {
return 'ok';
}
+
+ @bindThis
+ private async isPublicCollection(collection: string | ICollection | IOrderedCollection | undefined, resolver: Resolver): Promise<boolean> {
+ if (collection) {
+ const resolved = await resolver.resolveCollection(collection);
+ if (resolved.first || (resolved as ICollection).items || (resolved as IOrderedCollection).orderedItems) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 5b6c6c8ca6..131c518c0a 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -97,13 +97,15 @@ export interface IActivity extends IObject {
export interface ICollection extends IObject {
type: 'Collection';
totalItems: number;
- items: ApObject;
+ first?: IObject | string;
+ items?: ApObject;
}
export interface IOrderedCollection extends IObject {
type: 'OrderedCollection';
totalItems: number;
- orderedItems: ApObject;
+ first?: IObject | string;
+ orderedItems?: ApObject;
}
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event'];
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 7fd093c191..9bf568bc90 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -454,12 +454,12 @@ export class UserEntityService implements OnModuleInit {
}
const followingCount = profile == null ? null :
- (profile.followingVisibility === 'public') || isMe ? user.followingCount :
+ (profile.followingVisibility === 'public') || isMe || iAmModerator ? user.followingCount :
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null;
const followersCount = profile == null ? null :
- (profile.followersVisibility === 'public') || isMe ? user.followersCount :
+ (profile.followersVisibility === 'public') || isMe || iAmModerator ? user.followersCount :
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;