core&ui: add manage user priv (#433)

This commit is contained in:
panda authored and GitHub committed 2022-10-06 21:22:24 +08:00
1 parent b36adcdf13
commit a5494b1345
13 files changed
+274 -11

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "4.0.6",
"version": "4.0.7",
"bin": "bin/hydrooj.js",
"main": "src/plugin-api",
"module": "src/plugin-api",
+45 -1
View File
@@ -3,7 +3,9 @@ import { inspect } from 'util';
import * as yaml from 'js-yaml';
import Schema from 'schemastery';
import * as check from '../check';
import { BadRequestError, ValidationError } from '../error';
import {
BadRequestError, ForbiddenError, UserNotFoundError, ValidationError,
} from '../error';
import { isEmail, isPassword, isUname } from '../lib/validator';
import { Logger } from '../logger';
import { PRIV, STATUS } from '../model/builtin';
@@ -261,6 +263,47 @@ class SystemUserImportHandler extends SystemHandler {
}
}
const Priv = Object.fromEntries(Object.entries(PRIV).filter(
(i) => (!i[0].endsWith('SELF') && !['PRIV_DEFAULT', 'PRIV_NEVER', 'PRIV_NONE', 'PRIV_ALL'].includes(i[0])),
));
const allPriv = Math.sum(Object.values(Priv));
class SystemUserPrivHandler extends SystemHandler {
@requireSudo
async get({ pjax }) {
const defaultPriv = system.get('default.priv');
const udocs = await user.getMulti({ _id: { $gte: -1000, $ne: 1 }, priv: { $nin: [0, defaultPriv] } }).limit(1000).sort({ _id: 1 }).toArray();
const banudocs = await user.getMulti({ _id: { $gte: -1000, $ne: 1 }, priv: 0 }).limit(1000).sort({ _id: 1 }).toArray();
this.response.body = {
udocs: [...udocs, ...banudocs],
defaultPriv,
Priv,
};
if (pjax) {
const html = await this.renderHTML('partials/manage_user_priv.html', this.response.body);
this.response.body = { fragments: [{ html }] };
} else this.response.template = 'manage_user_priv.html';
}
@requireSudo
@param('uid', Types.Int, true)
@param('priv', Types.UnsignedInt)
async post(domainId: string, uid: number, priv: number) {
if (typeof uid === 'number') {
const udoc = await user.getById(domainId, uid);
if (!udoc) throw new UserNotFoundError(uid);
if (udoc.priv === -1 || priv === -1 || priv === allPriv) throw new ForbiddenError('you can not edit user as SU in web.');
await user.setPriv(uid, priv);
} else {
const defaultPriv = system.get('default.priv');
await user.coll.updateMany({ priv: defaultPriv }, { $set: { priv } });
await system.set('default.priv', priv);
bus.broadcast('user/delcache', true);
}
this.back();
}
}
export async function apply(ctx) {
ctx.Route('manage', '/manage', SystemMainHandler);
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
@@ -268,5 +311,6 @@ export async function apply(ctx) {
ctx.Route('manage_setting', '/manage/setting', SystemSettingHandler);
ctx.Route('manage_config', '/manage/config', SystemConfigHandler);
ctx.Route('manage_user_import', '/manage/userimport', SystemUserImportHandler);
ctx.Route('manage_user_priv', '/manage/userpriv', SystemUserPrivHandler);
ctx.Connection('manage_check', '/manage/check-conn', SystemCheckConnHandler);
}
+1 -1
View File
@@ -217,7 +217,7 @@ SystemSetting(
Setting('setting_limits', 'limit.submission_user', 15, 'number', 'limit.submission_user', 'Max submission count per user per minute'),
Setting('setting_limits', 'limit.pretest', 60, 'number', 'limit.pretest', 'Max pretest count per minute'),
Setting('setting_basic', 'avatar.gravatar_url', '//cn.gravatar.com/avatar/', 'text', 'avatar.gravatar_url', 'Gravatar URL Prefix'),
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege', FLAG_PRO),
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege', FLAG_HIDDEN),
Setting('setting_basic', 'discussion.nodes', builtin.DEFAULT_NODES, 'yaml', 'discussion.nodes', 'Discussion Nodes'),
Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'),
Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'),
+7 -4
View File
@@ -23,25 +23,27 @@ export const collV: Collection<VUdoc> = db.collection('vuser');
export const collGroup: Collection<GDoc> = db.collection('user.group');
const cache = new LRU<string, User>({ max: 10000, ttl: 300 * 1000 });
export function deleteUserCache(udoc: User | Udoc | string | undefined | null, receiver = false) {
if (!udoc) return;
export function deleteUserCache(udoc: User | Udoc | string | true | undefined | null, receiver = false) {
if (!udoc) return false;
if (!receiver) {
bus.broadcast(
'user/delcache',
JSON.stringify(typeof udoc === 'string' ? udoc : pick(udoc, ['uname', 'mail', '_id'])),
);
}
if (udoc === true) return cache.clear();
if (typeof udoc === 'string') {
// is domainId
for (const key of [...cache.keys()].filter((i) => i.endsWith(`/${udoc}`))) cache.delete(key);
return;
return true;
}
const id = [`id/${udoc._id.toString()}`, `name/${udoc.uname.toLowerCase()}`, `mail/${udoc.mail.toLowerCase()}`];
for (const key of [...cache.keys()].filter((k) => id.includes(`${k.split('/')[0]}/${k.split('/')[1]}`))) {
cache.delete(key);
}
return true;
}
bus.on('user/delcache', (content) => deleteUserCache(JSON.parse(content), true));
bus.on('user/delcache', (content) => deleteUserCache(typeof content === 'string' ? JSON.parse(content) : content, true));
export class User {
_id: number;
@@ -156,6 +158,7 @@ function handleMailLower(mail: string) {
}
class UserModel {
static coll = coll;
static User = User;
static defaultUser: Udoc = {
_id: 0,
+1 -1
View File
@@ -50,7 +50,7 @@ export interface EventMap extends LifecycleEvents, HandlerEvents {
'user/message': (uid: number, mdoc: MessageDoc) => void
'user/get': (udoc: User) => void
'user/delcache': (content: string) => void
'user/delcache': (content: string | true) => void
'domain/create': (ddoc: DomainDoc) => VoidReturn
'domain/before-get': (query: FilterQuery<DomainDoc>) => VoidReturn
+3 -2
View File
@@ -185,7 +185,7 @@ function _descriptor(v: ParamOption) {
: this.request.body;
const value = src[item.name];
if (!item.isOptional || value) {
if (!value) throw new ValidationError(item.name);
if (value === undefined || value === null || value === '') throw new ValidationError(item.name);
if (item.validate && !item.validate(value)) throw new ValidationError(item.name);
if (item.convert) c.push(item.convert(value));
else c.push(value);
@@ -216,7 +216,8 @@ export function requireSudo(target: any, funcName: string, obj: any) {
const originalMethod = obj.value;
obj.value = function sudo(this: Handler, ...args: any[]) {
if (this.session.sudo && Date.now() - this.session.sudo < Time.hour) {
this.request.headers.referer = this.session.sudoArgs.referer;
if (this.session.sudoArgs?.referer) this.request.headers.referer = this.session.sudoArgs.referer;
this.session.sudoArgs = null;
return originalMethod.call(this, ...args);
}
this.session.sudoArgs = {