feat(role): リモートのアイコンとバナーの変更をロールで制限できるように (MisskeyIO#374)

This commit is contained in:
まっちゃとーにゅ
2024-01-23 20:37:14 +09:00
committed by GitHub
parent e0c0a2a0b0
commit 65382dc70b
10 changed files with 94 additions and 5 deletions

View File

@@ -39,6 +39,8 @@ export type RolePolicies = {
canCreateContent: boolean;
canUpdateContent: boolean;
canDeleteContent: boolean;
canUpdateAvatar: boolean;
canUpdateBanner: boolean;
canInvite: boolean;
inviteLimit: number;
inviteLimitCycle: number;
@@ -70,6 +72,8 @@ export const DEFAULT_POLICIES: RolePolicies = {
canCreateContent: true,
canUpdateContent: true,
canDeleteContent: true,
canUpdateAvatar: true,
canUpdateBanner: true,
canInvite: false,
inviteLimit: 0,
inviteLimitCycle: 60 * 24 * 7,
@@ -337,6 +341,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canCreateContent: calc('canCreateContent', vs => vs.some(v => v === true)),
canUpdateContent: calc('canUpdateContent', vs => vs.some(v => v === true)),
canDeleteContent: calc('canDeleteContent', vs => vs.some(v => v === true)),
canUpdateAvatar: calc('canUpdateAvatar', vs => vs.some(v => v === true)),
canUpdateBanner: calc('canUpdateBanner', vs => vs.some(v => v === true)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),

View File

@@ -20,6 +20,7 @@ import type Logger from '@/logger.js';
import type { MiNote } from '@/models/Note.js';
import type { IdService } from '@/core/IdService.js';
import type { MfmService } from '@/core/MfmService.js';
import type { RoleService } from '@/core/RoleService.js';
import { toArray } from '@/misc/prelude/array.js';
import type { GlobalEventService } from '@/core/GlobalEventService.js';
import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@@ -75,6 +76,7 @@ export class ApPersonService implements OnModuleInit {
private instanceChart: InstanceChart;
private apLoggerService: ApLoggerService;
private accountMoveService: AccountMoveService;
private roleService: RoleService;
private logger: Logger;
constructor(
@@ -123,6 +125,7 @@ export class ApPersonService implements OnModuleInit {
this.instanceChart = this.moduleRef.get('InstanceChart');
this.apLoggerService = this.moduleRef.get('ApLoggerService');
this.accountMoveService = this.moduleRef.get('AccountMoveService');
this.roleService = this.moduleRef.get('RoleService');
this.logger = this.apLoggerService.logger;
}
@@ -462,6 +465,8 @@ export class ApPersonService implements OnModuleInit {
throw new Error('unexpected schema of person url: ' + url);
}
const policy = await this.roleService.getUserPolicies(exist.id);
const updates = {
lastFetchedAt: new Date(),
inbox: person.inbox,
@@ -477,7 +482,7 @@ export class ApPersonService implements OnModuleInit {
movedToUri: person.movedTo ?? null,
alsoKnownAs: person.alsoKnownAs ?? null,
isExplorable: person.discoverable,
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
...((policy.canUpdateAvatar || policy.canUpdateBanner) ? await this.resolveAvatarAndBanner(exist, policy.canUpdateAvatar ? person.icon : exist.avatarUrl, policy.canUpdateBanner ? person.image : exist.bannerUrl).catch(() => ({})) : {}),
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
const moving = ((): boolean => {

View File

@@ -233,6 +233,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const updates = {} as Partial<MiUser>;
const profileUpdates = {} as Partial<MiUserProfile>;
const policy = await this.roleService.getUserPolicies(user.id);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
@@ -245,7 +246,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility;
if (ps.mutedWords !== undefined) {
const length = ps.mutedWords.length;
if (length > (await this.roleService.getUserPolicies(user.id)).wordMuteLimit) {
if (length > policy.wordMuteLimit) {
throw new ApiError(meta.errors.tooManyMutedWords);
}
@@ -279,13 +280,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
if (typeof ps.alwaysMarkNsfw === 'boolean') {
if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
if (policy.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
}
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
if (ps.avatarId) {
if (!policy.canUpdateAvatar) throw new ApiError(meta.errors.restrictedByRole);
const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
@@ -301,6 +303,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.bannerId) {
if (!policy.canUpdateBanner) throw new ApiError(meta.errors.restrictedByRole);
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
@@ -317,13 +320,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.avatarDecorations) {
const decorations = await this.avatarDecorationService.getAll(true);
const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);
const myRoles = await this.roleService.getUserRoles(user.id);
const allRoles = await this.roleService.getRoles();
const decorationIds = decorations
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
.map(d => d.id);
if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
if (ps.avatarDecorations.length > policy.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
id: d.id,