ロールにNSFWを強制的につけるオプションを追加 (#10731)
* ロールにNSFWを強制的につけるオプションを追加 * すでにあるファイルにNSFWが付与できない * NSFWを付与しようとするとエラーに * add test * Update packages/backend/src/core/RoleService.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * spacingで怒られたので * ロール作成時のプロパティ削除 --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
		| @@ -449,7 +449,12 @@ export class DriveService { | ||||
| 	}: AddFileArgs): Promise<DriveFile> { | ||||
| 		let skipNsfwCheck = false; | ||||
| 		const instance = await this.metaService.fetch(); | ||||
| 		if (user == null) skipNsfwCheck = true; | ||||
| 		const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw; | ||||
| 		if (user == null) { | ||||
| 			skipNsfwCheck = true; | ||||
| 		} else if (userRoleNSFW) { | ||||
| 			skipNsfwCheck = true; | ||||
| 		} | ||||
| 		if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true; | ||||
| 		if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true; | ||||
| 		if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true; | ||||
| @@ -571,6 +576,7 @@ export class DriveService { | ||||
|  | ||||
| 		if (info.sensitive && profile!.autoSensitive) file.isSensitive = true; | ||||
| 		if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true; | ||||
| 		if (userRoleNSFW) file.isSensitive = true; | ||||
|  | ||||
| 		if (url !== null) { | ||||
| 			file.src = url; | ||||
|   | ||||
| @@ -25,6 +25,7 @@ export type RolePolicies = { | ||||
| 	canSearchNotes: boolean; | ||||
| 	canHideAds: boolean; | ||||
| 	driveCapacityMb: number; | ||||
| 	alwaysMarkNsfw: boolean; | ||||
| 	pinLimit: number; | ||||
| 	antennaLimit: number; | ||||
| 	wordMuteLimit: number; | ||||
| @@ -45,6 +46,7 @@ export const DEFAULT_POLICIES: RolePolicies = { | ||||
| 	canSearchNotes: false, | ||||
| 	canHideAds: false, | ||||
| 	driveCapacityMb: 100, | ||||
| 	alwaysMarkNsfw: false, | ||||
| 	pinLimit: 5, | ||||
| 	antennaLimit: 5, | ||||
| 	wordMuteLimit: 200, | ||||
| @@ -279,6 +281,7 @@ export class RoleService implements OnApplicationShutdown { | ||||
| 			canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), | ||||
| 			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)), | ||||
| 			pinLimit: calc('pinLimit', vs => Math.max(...vs)), | ||||
| 			antennaLimit: calc('antennaLimit', vs => Math.max(...vs)), | ||||
| 			wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)), | ||||
|   | ||||
| @@ -150,7 +150,7 @@ export class ApNoteService { | ||||
| 		if (actor.isSuspended) { | ||||
| 			throw new Error('actor has been suspended'); | ||||
| 		} | ||||
| 	 | ||||
| 		 | ||||
| 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); | ||||
| 		let visibility = noteAudience.visibility; | ||||
| 		const visibleUsers = noteAudience.visibleUsers; | ||||
|   | ||||
| @@ -40,8 +40,13 @@ export const meta = { | ||||
| 			code: 'NO_SUCH_FOLDER', | ||||
| 			id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73', | ||||
| 		}, | ||||
| 		 | ||||
| 		restrictedByRole: { | ||||
| 			message: 'This feature is restricted by your role.', | ||||
| 			code: 'RESTRICTED_BY_ROLE', | ||||
| 			id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7', | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	res: { | ||||
| 		type: 'object', | ||||
| 		optional: false, nullable: false, | ||||
| @@ -77,7 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); | ||||
|  | ||||
| 			const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw; | ||||
| 			if (file == null) { | ||||
| 				throw new ApiError(meta.errors.noSuchFile); | ||||
| 			} | ||||
| @@ -93,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||
|  | ||||
| 			if (ps.comment !== undefined) file.comment = ps.comment; | ||||
|  | ||||
| 			if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) { | ||||
| 				throw new ApiError(meta.errors.restrictedByRole); | ||||
| 			} | ||||
|  | ||||
| 			if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; | ||||
|  | ||||
| 			if (ps.folderId !== undefined) { | ||||
|   | ||||
| @@ -93,6 +93,12 @@ export const meta = { | ||||
| 			code: 'FORBIDDEN_TO_SET_YOURSELF', | ||||
| 			id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4', | ||||
| 		}, | ||||
|  | ||||
| 		restrictedByRole: { | ||||
| 			message: 'This feature is restricted by your role.', | ||||
| 			code: 'RESTRICTED_BY_ROLE', | ||||
| 			id: '8feff0ba-5ab5-585b-31f4-4df816663fad', | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	res: { | ||||
| @@ -239,7 +245,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||
| 			if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; | ||||
| 			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; | ||||
| 			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; | ||||
| 			if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; | ||||
| 			if (typeof ps.alwaysMarkNsfw === 'boolean') { | ||||
| 				if ((await roleService.getUserPolicies(user.id)).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; | ||||
|  | ||||
|   | ||||
| @@ -352,6 +352,72 @@ describe('Note', () => { | ||||
| 			assert.strictEqual(myNote.renote.reply.files.length, 1); | ||||
| 			assert.strictEqual(myNote.renote.reply.files[0].id, file.body.id); | ||||
| 		}); | ||||
|  | ||||
| 		test('NSFWが強制されている場合変更できない', async () => { | ||||
| 			const file = await uploadFile(alice); | ||||
|  | ||||
| 			const res = await api('admin/roles/create', { | ||||
| 				name: 'test', | ||||
| 				description: '', | ||||
| 				color: null, | ||||
| 				iconUrl: null, | ||||
| 				displayOrder: 0, | ||||
| 				target: 'manual', | ||||
| 				condFormula: {}, | ||||
| 				isAdministrator: false, | ||||
| 				isModerator: false, | ||||
| 				isPublic: false, | ||||
| 				isExplorable: false, | ||||
| 				asBadge: false, | ||||
| 				canEditMembersByModerator: false, | ||||
| 				policies: { | ||||
| 					alwaysMarkNsfw: { | ||||
| 						useDefault: false, | ||||
| 						priority: 0, | ||||
| 						value: true, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, alice); | ||||
| 			 | ||||
| 			assert.strictEqual(res.status, 200); | ||||
|  | ||||
| 			const assign = await api('admin/roles/assign', { | ||||
| 				userId: alice.id, | ||||
| 				roleId: res.body.id, | ||||
| 			}, alice); | ||||
|  | ||||
| 			assert.strictEqual(assign.status, 204); | ||||
| 			assert.strictEqual(file.body.isSensitive, false); | ||||
|  | ||||
| 			const nsfwfile = await uploadFile(alice); | ||||
|  | ||||
| 			assert.strictEqual(nsfwfile.status, 200); | ||||
| 			assert.strictEqual(nsfwfile.body.isSensitive, true); | ||||
|  | ||||
| 			const liftnsfw = await api('drive/files/update', { | ||||
| 				fileId: nsfwfile.body.id, | ||||
| 				isSensitive: false, | ||||
| 			}, alice); | ||||
|  | ||||
| 			assert.strictEqual(liftnsfw.status, 400); | ||||
| 			assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE'); | ||||
|  | ||||
| 			const oldaddnsfw = await api('drive/files/update', { | ||||
| 				fileId: file.body.id, | ||||
| 				isSensitive: true, | ||||
| 			}, alice); | ||||
|  | ||||
| 			assert.strictEqual(oldaddnsfw.status, 200); | ||||
|  | ||||
| 			await api('admin/roles/unassign', { | ||||
| 				userId: alice.id, | ||||
| 				roleId: res.body.id, | ||||
| 			}); | ||||
|  | ||||
| 			await api('admin/roles/delete', { | ||||
| 				roleId: res.body.id, | ||||
| 			}, alice); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	describe('notes/create', () => { | ||||
|   | ||||
| @@ -61,6 +61,7 @@ export const ROLE_POLICIES = [ | ||||
| 	'canSearchNotes', | ||||
| 	'canHideAds', | ||||
| 	'driveCapacityMb', | ||||
| 	'alwaysMarkNsfw', | ||||
| 	'pinLimit', | ||||
| 	'antennaLimit', | ||||
| 	'wordMuteLimit', | ||||
|   | ||||
| @@ -210,7 +210,7 @@ | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
| 			</MkFolder> | ||||
|  | ||||
| 			 | ||||
| 			<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])"> | ||||
| 				<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> | ||||
| 				<template #suffix> | ||||
| @@ -231,6 +231,26 @@ | ||||
| 				</div> | ||||
| 			</MkFolder> | ||||
|  | ||||
| 			<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])"> | ||||
| 				<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template> | ||||
| 				<template #suffix> | ||||
| 					<span v-if="role.policies.alwaysMarkNsfw.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> | ||||
| 					<span v-else>{{ role.policies.alwaysMarkNsfw.value ? i18n.ts.yes : i18n.ts.no }}</span> | ||||
| 					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.alwaysMarkNsfw)"></i></span> | ||||
| 				</template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="role.policies.alwaysMarkNsfw.value" :disabled="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
| 			</MkFolder> | ||||
|  | ||||
| 			<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])"> | ||||
| 				<template #label>{{ i18n.ts._role._options.pinMax }}</template> | ||||
| 				<template #suffix> | ||||
|   | ||||
| @@ -75,6 +75,14 @@ | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
|  | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template> | ||||
| 							<template #suffix>{{ policies.alwaysMarkNsfw ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.alwaysMarkNsfw"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
|  | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.pinMax }}</template> | ||||
| 							<template #suffix>{{ policies.pinLimit }}</template> | ||||
|   | ||||
| @@ -119,6 +119,13 @@ function saveProfile() { | ||||
| 	os.api('i/update', { | ||||
| 		alwaysMarkNsfw: !!alwaysMarkNsfw, | ||||
| 		autoSensitive: !!autoSensitive, | ||||
| 	}).catch(err => { | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
| 			title: i18n.ts.error, | ||||
| 			text: err.message, | ||||
| 		}); | ||||
| 		alwaysMarkNsfw = true; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,12 @@ function toggleSensitive(file: Misskey.entities.DriveFile) { | ||||
| 	os.api('drive/files/update', { | ||||
| 		fileId: file.id, | ||||
| 		isSensitive: !file.isSensitive, | ||||
| 	}).catch(err => { | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
| 			title: i18n.ts.error, | ||||
| 			text: err.message, | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 nenohi
					nenohi