From 65f0d53270758f1dc0b0689803e607c50859ed2c Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:22:58 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3/=E3=83=90?= =?UTF-8?q?=E3=83=8A=E3=83=BC=E3=81=AE=E5=A4=89=E6=9B=B4=E5=8F=AF=E5=90=A6?= =?UTF-8?q?=E3=82=92=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=A7=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E3=81=AB=20(#207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement role policy "canUpdateBioMedia" * docs(changelog): update changelog * docs(changelog): update changelog * chore: regenerate misskey-js type definitions * chore: Apply suggestion from code review Co-authored-by: anatawa12 --------- Co-authored-by: anatawa12 --- CHANGELOG.md | 2 ++ locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + packages/backend/src/core/RoleService.ts | 3 +++ .../activitypub/models/ApPersonService.ts | 8 ++++++++ .../backend/src/models/json-schema/role.ts | 4 ++++ .../src/server/api/endpoints/i/update.ts | 4 ++++ packages/frontend/src/const.ts | 1 + .../frontend/src/pages/admin/roles.editor.vue | 20 +++++++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 8 ++++++++ packages/misskey-js/src/autogen/types.ts | 1 + 11 files changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3edae44ec59..3a40eddc5069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### General - 未知のリモートユーザーによる通知が発生するノートをブロックする機能の改善 (#206) +- Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に + - 変更不可となっていても、設定済みのものを解除してデフォルト画像に戻すことは出来ます ### Client diff --git a/locales/index.d.ts b/locales/index.d.ts index b4830a403d9e..f256901d00d5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6630,6 +6630,10 @@ export interface Locale extends ILocale { * ファイルにNSFWを常に付与 */ "alwaysMarkNsfw": string; + /** + * アイコンとバナーの更新を許可 + */ + "canUpdateBioMedia": string; /** * ノートのピン留めの最大数 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d5c7841ace4b..819f3ed0a497 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1714,6 +1714,7 @@ _role: canManageAvatarDecorations: "アバターデコレーションの管理" driveCapacity: "ドライブ容量" alwaysMarkNsfw: "ファイルにNSFWを常に付与" + canUpdateBioMedia: "アイコンとバナーの更新を許可" pinMax: "ノートのピン留めの最大数" antennaMax: "アンテナの作成可能数" wordMuteMax: "ワードミュートの最大文字数" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 15d184673c7a..be185b8d3920 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -48,6 +48,7 @@ export type RolePolicies = { canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; + canUpdateBioMedia: boolean; pinLimit: number; antennaLimit: number; wordMuteLimit: number; @@ -77,6 +78,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canHideAds: false, driveCapacityMb: 100, alwaysMarkNsfw: false, + canUpdateBioMedia: true, pinLimit: 5, antennaLimit: 5, wordMuteLimit: 200, @@ -379,6 +381,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), + canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)), pinLimit: calc('pinLimit', vs => Math.max(...vs)), antennaLimit: calc('antennaLimit', vs => Math.max(...vs)), wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)), diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 744b1ea68376..b17beadc1779 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -34,6 +34,7 @@ import { StatusError } from '@/misc/status-error.js'; import type { UtilityService } from '@/core/UtilityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { AccountMoveService } from '@/core/AccountMoveService.js'; @@ -101,6 +102,8 @@ export class ApPersonService implements OnModuleInit { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + + private roleService: RoleService, ) { } @@ -239,6 +242,11 @@ export class ApPersonService implements OnModuleInit { return this.apImageService.resolveImage(user, img).catch(() => null); })); + if (((avatar != null && avatar.id !== null) || (banner != null && banner.id !== null)) + && !(await this.roleService.getUserPolicies(user.id)).canUpdateBioMedia) { + return {}; + } + /* we don't want to return nulls on errors! if the database fields are already null, nothing changes; if the database has old diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index c3d22137b2aa..bf4cf52c8bf8 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -232,6 +232,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canUpdateBioMedia: { + type: 'boolean', + optional: false, nullable: false, + }, pinLimit: { type: 'integer', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index a8e702f328e6..3c3b865669e8 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -322,6 +322,8 @@ export default class extends Endpoint { // eslint- if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; if (ps.avatarId) { + if (!(await roleService.getUserPolicies(user.id)).canUpdateBioMedia) 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); @@ -337,6 +339,8 @@ export default class extends Endpoint { // eslint- } if (ps.bannerId) { + if (!(await roleService.getUserPolicies(user.id)).canUpdateBioMedia) 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); diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index fa6fd253a851..e75bd3e53d48 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -88,6 +88,7 @@ export const ROLE_POLICIES = [ 'canHideAds', 'driveCapacityMb', 'alwaysMarkNsfw', + 'canUpdateBioMedia', 'pinLimit', 'antennaLimit', 'wordMuteLimit', diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index cc066d445090..16b7a72e1ac9 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -398,6 +398,26 @@ SPDX-License-Identifier: AGPL-3.0-only + + + +
+ + + + + + + + + +
+
+