移除权限节点,添加 get post route 修饰器

PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION
PERM_EDIT_DISCUSSION_REPLY
PERM_EDIT_PROBLEM_SOLUTION_REPLY
This commit is contained in:
undefined committed 2020-09-25 23:13:35 +08:00
1 parent 6dd7a4dcb8
commit 99b7d33157
8 files changed
+120 -63

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@
"lint": "eslint packages --ext ts --fix", "lint": "eslint packages --ext ts --fix",
"pub": "node build/publish", "pub": "node build/publish",
"jest": "node build/jest", "jest": "node build/jest",
"debug": "node --async-stack-traces --trace-deprecation --enable-source-maps build/start", "debug": "node --async-stack-traces --trace-deprecation --enable-source-maps build/start --debug",
"start": "node build/start", "start": "node build/start",
"clean": "zsh -c \"rm -r tsconfig.build.tsbuildinfo ./packages/*/tsconfig.tsbuildinfo ./packages/*/dist ./.coverage\"" "clean": "zsh -c \"rm -r tsconfig.build.tsbuildinfo ./packages/*/tsconfig.tsbuildinfo ./packages/*/dist ./.coverage\""
}, },
+3 -2
View File
@@ -9,5 +9,6 @@ message.markAsRead
评测队列优先级系统 评测队列优先级系统
讨论浏览数量显示 讨论浏览数量显示
解离mail模块 解离mail模块
@param @get @post @arg 存储系统抽象化
extend problem settings extend problem settings
+oplog
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.13.16", "version": "2.13.17",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"main": "dist/loader.js", "main": "dist/loader.js",
"typings": "dist/loader.d.ts", "typings": "dist/loader.d.ts",
+5 -7
View File
@@ -1,6 +1,6 @@
import { ObjectID } from 'mongodb'; import { ObjectID } from 'mongodb';
import { isSafeInteger } from 'lodash'; import { isSafeInteger } from 'lodash';
import { DiscussionNotFoundError, DocumentNotFoundError } from '../error'; import { DiscussionNotFoundError, DocumentNotFoundError, PermissionError } from '../error';
import { import {
Mdoc, Drdoc, Ddoc, Drrdoc, Mdoc, Drdoc, Ddoc, Drrdoc,
} from '../interface'; } from '../interface';
@@ -242,11 +242,8 @@ class DiscussionDetailHandler extends DiscussionHandler {
@param('drid', Types.ObjectID) @param('drid', Types.ObjectID)
@param('content', Types.String, isContent) @param('content', Types.String, isContent)
async postEditReply(domainId: string, drid: ObjectID, content: string) { async postEditReply(domainId: string, drid: ObjectID, content: string) {
if (!(this.ddoc.owner === this.user._id this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF);
&& this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION))) { if (this.drdoc.owner !== this.user._id) throw new PermissionError(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF);
if (this.drdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY);
else this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF);
}
await discussion.editReply(domainId, drid, content); await discussion.editReply(domainId, drid, content);
this.back(); this.back();
} }
@@ -267,7 +264,8 @@ class DiscussionDetailHandler extends DiscussionHandler {
@param('drrid', Types.ObjectID) @param('drrid', Types.ObjectID)
@param('content', Types.String, isContent) @param('content', Types.String, isContent)
async postEditTailReply(domainId: string, drid: ObjectID, drrid: ObjectID, content: string) { async postEditTailReply(domainId: string, drid: ObjectID, drrid: ObjectID, content: string) {
if (this.drdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY); this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF);
if (this.drdoc.owner !== this.user._id) throw new PermissionError(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF);
await discussion.editTailReply(domainId, drid, drrid, content); await discussion.editTailReply(domainId, drid, drrid, content);
this.back(); this.back();
} }
+1 -1
View File
@@ -556,7 +556,7 @@ export class ProblemSolutionHandler extends ProblemDetailHandler {
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid); if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
if (!(psrdoc.owner === this.user._id if (!(psrdoc.owner === this.user._id
&& this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF))) { && this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF))) {
this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY); throw new PermissionError(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF);
} }
await solution.editReply(domainId, psid, psrid, content); await solution.editReply(domainId, psid, psrid, content);
this.back(); this.back();
+2
View File
@@ -140,4 +140,6 @@ export class Logger {
} }
} }
// This fails in jest
// @ts-ignore
global.Hydro.Logger = { Logger }; global.Hydro.Logger = { Logger };
+1 -6
View File
@@ -37,7 +37,6 @@ export const PERM = {
PERM_DELETE_PROBLEM_SOLUTION: 1n << 20n, PERM_DELETE_PROBLEM_SOLUTION: 1n << 20n,
PERM_DELETE_PROBLEM_SOLUTION_SELF: 1n << 21n, PERM_DELETE_PROBLEM_SOLUTION_SELF: 1n << 21n,
PERM_REPLY_PROBLEM_SOLUTION: 1n << 22n, PERM_REPLY_PROBLEM_SOLUTION: 1n << 22n,
PERM_EDIT_PROBLEM_SOLUTION_REPLY: 1n << 23n,
PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF: 1n << 24n, PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF: 1n << 24n,
PERM_DELETE_PROBLEM_SOLUTION_REPLY: 1n << 25n, PERM_DELETE_PROBLEM_SOLUTION_REPLY: 1n << 25n,
PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF: 1n << 26n, PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF: 1n << 26n,
@@ -52,9 +51,7 @@ export const PERM = {
PERM_DELETE_DISCUSSION: 1n << 32n, PERM_DELETE_DISCUSSION: 1n << 32n,
PERM_DELETE_DISCUSSION_SELF: 1n << 33n, PERM_DELETE_DISCUSSION_SELF: 1n << 33n,
PERM_REPLY_DISCUSSION: 1n << 34n, PERM_REPLY_DISCUSSION: 1n << 34n,
PERM_EDIT_DISCUSSION_REPLY: 1n << 35n,
PERM_EDIT_DISCUSSION_REPLY_SELF: 1n << 36n, PERM_EDIT_DISCUSSION_REPLY_SELF: 1n << 36n,
PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 37n,
PERM_DELETE_DISCUSSION_REPLY: 1n << 38n, PERM_DELETE_DISCUSSION_REPLY: 1n << 38n,
PERM_DELETE_DISCUSSION_REPLY_SELF: 1n << 39n, PERM_DELETE_DISCUSSION_REPLY_SELF: 1n << 39n,
PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 40n, PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 40n,
@@ -118,7 +115,6 @@ export const PERMS = [
Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION, 'Delete problem solutions'), Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION, 'Delete problem solutions'),
Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF, 'Delete own problem solutions'), Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF, 'Delete own problem solutions'),
Permission('perm_problem_solution', PERM.PERM_REPLY_PROBLEM_SOLUTION, 'Reply problem solutions'), Permission('perm_problem_solution', PERM.PERM_REPLY_PROBLEM_SOLUTION, 'Reply problem solutions'),
Permission('perm_problem_solution', PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY, 'Edit problem solution replies'),
Permission('perm_problem_solution', PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF, 'Edit own problem solution replies'), Permission('perm_problem_solution', PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF, 'Edit own problem solution replies'),
Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY, 'Delete problem solution replies'), Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY, 'Delete problem solution replies'),
Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF, 'Delete own problem solution replies'), Permission('perm_problem_solution', PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF, 'Delete own problem solution replies'),
@@ -130,9 +126,7 @@ export const PERMS = [
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION, 'Delete discussions'), Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION, 'Delete discussions'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_SELF, 'Delete own discussions'), Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_SELF, 'Delete own discussions'),
Permission('perm_discussion', PERM.PERM_REPLY_DISCUSSION, 'Reply discussions'), Permission('perm_discussion', PERM.PERM_REPLY_DISCUSSION, 'Reply discussions'),
Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION_REPLY, 'Edit discussion replies'),
Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION_REPLY_SELF, 'Edit own discussion replies'), Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION_REPLY_SELF, 'Edit own discussion replies'),
Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION, 'Edit discussion replies of own discussion'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY, 'Delete discussion replies'), Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY, 'Delete discussion replies'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY_SELF, 'Delete own discussion replies'), Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY_SELF, 'Delete own discussion replies'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION, 'Delete discussion replies of own discussion'), Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION, 'Delete discussion replies of own discussion'),
@@ -627,6 +621,7 @@ export const CONSTANT = {
global.Hydro.model.builtin = { global.Hydro.model.builtin = {
Permission, Permission,
registerLanguage, registerLanguage,
CONSTANT,
PERM, PERM,
PERMS, PERMS,
PERMS_BY_FAMILY, PERMS_BY_FAMILY,
+106 -45
View File
@@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */ /* eslint-disable prefer-destructuring */
import assert from 'assert'; import assert from 'assert';
import { PassThrough } from 'stream'; import { PassThrough } from 'stream';
import path from 'path'; import { resolve } from 'path';
import os from 'os'; import os from 'os';
import http from 'http'; import http from 'http';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
@@ -43,6 +43,7 @@ type Converter = (value: any) => any;
type Validator = (value: any) => boolean; type Validator = (value: any) => boolean;
interface ParamOption { interface ParamOption {
name: string, name: string,
source: 'all' | 'get' | 'post' | 'route',
isOptional?: boolean, isOptional?: boolean,
convert?: Converter, convert?: Converter,
validate?: Validator, validate?: Validator,
@@ -130,59 +131,52 @@ export const Types: Types = {
], ],
}; };
export function param(name: string, type: Type, validate: Validator): MethodDecorator; function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...args: Array<Type | boolean | Validator | Converter>) {
export function param(name: string, type?: Type, isOptional?: boolean): MethodDecorator;
export function param(
name: string, type: Type, validate: null, convert: Converter
): MethodDecorator;
export function param(
name: string, type: Type, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function param(
name: string, type: Type, isOptional?: boolean, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function param(
name: string, ...args: Array<Type | boolean | Converter | Validator>
): MethodDecorator;
export function param(name: string, ...args: any): MethodDecorator {
let cursor = 0; let cursor = 0;
const v: ParamOption = { name }; const v: ParamOption = { name, source };
let isValidate = true; let isValidate = true;
while (cursor < args.length) { while (cursor < args.length) {
if (args[cursor] instanceof Array) { const current = args[cursor];
const type = args[cursor]; if (current instanceof Array) {
const type = current;
if (type[0]) v.convert = type[0]; if (type[0]) v.convert = type[0];
if (type[1]) v.validate = type[1]; if (type[1]) v.validate = type[1];
if (type[2]) v.isOptional = type[2]; if (type[2]) v.isOptional = type[2];
} else if (typeof args[cursor] === 'boolean') v.isOptional = args[cursor]; } else if (typeof current === 'boolean') v.isOptional = current;
else if (isValidate) { else if (isValidate) {
if (args[cursor] !== null) v.validate = args[cursor]; if (current !== null) v.validate = current;
isValidate = false; isValidate = false;
} else { } else v.convert = current;
v.convert = args[cursor];
}
cursor++; cursor++;
} }
return function desc(target: any, funcName: string, obj: any) { return v;
}
function _descriptor(v: ParamOption) {
return function desc(this: Handler, target: any, funcName: string, obj: any) {
if (!target.__param) target.__param = {}; if (!target.__param) target.__param = {};
if (!target.__param[target.constructor.name]) target.__param[target.constructor.name] = {}; if (!target.__param[target.constructor.name]) target.__param[target.constructor.name] = {};
if (!target.__param[target.constructor.name][funcName]) { if (!target.__param[target.constructor.name][funcName]) {
target.__param[target.constructor.name][funcName] = [{ name: 'domainId', type: 'string' }]; target.__param[target.constructor.name][funcName] = [{ name: 'domainId', type: 'string', source: 'route' }];
const originalMethod = obj.value; const originalMethod = obj.value;
obj.value = function validate(rawArgs: any, ...extra: any[]) { obj.value = function validate(this: Handler, rawArgs: any, ...extra: any[]) {
if (typeof rawArgs === 'string' || extra.length) return originalMethod.call(this, rawArgs, ...extra); if (typeof rawArgs !== 'object' || extra.length) return originalMethod.call(this, rawArgs, ...extra);
const c = []; const c = [];
const arglist: ParamOption[] = this.__param[target.constructor.name][funcName]; const arglist: ParamOption[] = this.__param[target.constructor.name][funcName];
for (const item of arglist) { for (const item of arglist) {
if (!item.isOptional || rawArgs[item.name]) { const src = item.source === 'all'
if (!rawArgs[item.name]) throw new ValidationError(item.name); ? rawArgs
if (item.validate) { : item.source === 'get'
if (!item.validate(rawArgs[item.name])) { ? this.request.query
throw new ValidationError(item.name); : item.source === 'route'
} ? this.request.params
} : this.request.body;
if (item.convert) c.push(item.convert(rawArgs[item.name])); const value = src[item.name];
else c.push(rawArgs[item.name]); if (!item.isOptional || value) {
if (!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);
} else c.push(undefined); } else c.push(undefined);
} }
return originalMethod.call(this, ...c); return originalMethod.call(this, ...c);
@@ -193,6 +187,70 @@ export function param(name: string, ...args: any): MethodDecorator {
}; };
} }
export function get(
name: string, type: Type, validate: null, convert: Converter
): MethodDecorator;
export function get(
name: string, type: Type, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function get(
name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function get(
name: string, ...args: Array<Type | boolean | Validator | Converter>
): MethodDecorator;
export function get(name: string, ...args: any): MethodDecorator {
return _descriptor(_buildParam(name, 'get', ...args));
}
export function post(
name: string, type: Type, validate: null, convert: Converter
): MethodDecorator;
export function post(
name: string, type?: Type, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function post(
name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function post(
name: string, ...args: Array<Type | boolean | Converter | Validator>
): MethodDecorator;
export function post(name: string, ...args: any): MethodDecorator {
return _descriptor(_buildParam(name, 'post', ...args));
}
export function route(
name: string, type: Type, validate: null, convert: Converter
): MethodDecorator;
export function route(
name: string, type?: Type, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function route(
name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function route(
name: string, ...args: Array<Type | boolean | Converter | Validator>
): MethodDecorator;
export function route(name: string, ...args: any): MethodDecorator {
return _descriptor(_buildParam(name, 'route', ...args));
}
export function param(
name: string, type: Type, validate: null, convert: Converter
): MethodDecorator;
export function param(
name: string, type?: Type, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function param(
name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter,
): MethodDecorator;
export function param(
name: string, ...args: Array<Type | boolean | Converter | Validator>
): MethodDecorator;
export function param(name: string, ...args: any): MethodDecorator {
return _descriptor(_buildParam(name, 'all', ...args));
}
export function requireCsrfToken(target: any, funcName: string, obj: any) { export function requireCsrfToken(target: any, funcName: string, obj: any) {
const originalMethod = obj.value; const originalMethod = obj.value;
obj.value = async function checkCsrfToken(...args: any[]) { obj.value = async function checkCsrfToken(...args: any[]) {
@@ -211,7 +269,7 @@ export async function prepare() {
maxAge: 0, maxAge: 0,
})); }));
} else { } else {
app.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), { app.use(cache(resolve(os.tmpdir(), 'hydro', 'public'), {
maxAge: 365 * 24 * 60 * 60, maxAge: 365 * 24 * 60 * 60,
})); }));
} }
@@ -265,6 +323,8 @@ export class Handler {
loginMethods: any; loginMethods: any;
__param: Record<string, ParamOption[]>;
constructor(ctx: Koa.Context) { constructor(ctx: Koa.Context) {
this.ctx = ctx; this.ctx = ctx;
this.request = { this.request = {
@@ -624,10 +684,10 @@ const Checker = (permPrivChecker) => {
}; };
}; };
export function Route(name: string, route: string, RouteHandler: any, ...permPrivChecker) { export function Route(name: string, path: string, RouteHandler: any, ...permPrivChecker) {
const checker = Checker(permPrivChecker); const checker = Checker(permPrivChecker);
router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker)); router.all(name, path, (ctx) => handle(ctx, RouteHandler, checker));
router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker)); router.all(`${name}_with_domainId`, `/d/:domainId${path}`, (ctx) => handle(ctx, RouteHandler, checker));
} }
export class ConnectionHandler { export class ConnectionHandler {
@@ -772,10 +832,8 @@ export function Connection(
try { try {
const args = { domainId: 'system', ...h.request.params }; const args = { domainId: 'system', ...h.request.params };
h.args = args; h.args = args;
const cookie = await new Promise((resolve) => { const cookie = await new Promise((r) => {
conn.once('data', (c) => { conn.once('data', r);
resolve(c);
});
}); });
args.cookie = cookie; args.cookie = cookie;
await h.init(args); await h.init(args);
@@ -842,6 +900,9 @@ global.Hydro.service.server = {
app, app,
server, server,
router, router,
get,
post,
route,
param, param,
requireCsrfToken, requireCsrfToken,
Handler, Handler,