Skip to content

Commit

Permalink
ロールにNSFWを強制的につけるオプションを追加 (#10731)
Browse files Browse the repository at this point in the history
* ロールにNSFWを強制的につけるオプションを追加

* すでにあるファイルにNSFWが付与できない

* NSFWを付与しようとするとエラーに

* add test

* Update packages/backend/src/core/RoleService.ts

Co-authored-by: syuilo <[email protected]>

* spacingで怒られたので

* ロール作成時のプロパティ削除

---------

Co-authored-by: syuilo <[email protected]>
  • Loading branch information
nenohi and syuilo authored May 5, 2023
1 parent be226ec commit 2d84e04
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
- カスタム絵文字のライセンスを複数でセットできるようになりました。
- 管理者が予約ユーザー名を設定できるようになりました。
- Fix: フォローリクエストの通知が残る問題を修正
- ロールに強制的にNSFWを付与する設定を追加
* アップロード済みのファイルはNSFWにならない為注意してください。

### Client
- チャンネル内検索ができるように
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,7 @@ _role:
canInvite: "サーバー招待コードの発行"
canManageCustomEmojis: "カスタム絵文字の管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
pinMax: "ノートのピン留めの最大数"
antennaMax: "アンテナの作成可能数"
wordMuteMax: "ワードミュートの最大文字数"
Expand Down
8 changes: 7 additions & 1 deletion packages/backend/src/core/DriveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type RolePolicies = {
canSearchNotes: boolean;
canHideAds: boolean;
driveCapacityMb: number;
alwaysMarkNsfw: boolean;
pinLimit: number;
antennaLimit: number;
wordMuteLimit: number;
Expand All @@ -45,6 +46,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canSearchNotes: false,
canHideAds: false,
driveCapacityMb: 100,
alwaysMarkNsfw: false,
pinLimit: 5,
antennaLimit: 5,
wordMuteLimit: 200,
Expand Down Expand Up @@ -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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions packages/backend/src/server/api/endpoints/drive/files/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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;

Expand Down
66 changes: 66 additions & 0 deletions packages/backend/test/e2e/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const ROLE_POLICIES = [
'canSearchNotes',
'canHideAds',
'driveCapacityMb',
'alwaysMarkNsfw',
'pinLimit',
'antennaLimit',
'wordMuteLimit',
Expand Down
22 changes: 21 additions & 1 deletion packages/frontend/src/pages/admin/roles.editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/src/pages/admin/roles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions packages/frontend/src/pages/settings/drive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/scripts/get-drive-file-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
});
}

Expand Down

0 comments on commit 2d84e04

Please sign in to comment.