diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index a0aef213e..a59fe8b34 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -119,24 +119,25 @@ export class ProblemModel { static async add( domainId: string, pid: string = '', title: string, content: string, owner: number, - tag: string[] = [], hidden = false, + tag: string[] = [], meta: { difficulty?: number, hidden?: boolean } = {}, ) { const [doc] = await ProblemModel.getMulti(domainId, {}) .sort({ docId: -1 }).limit(1).project({ docId: 1 }) .toArray(); const result = await ProblemModel.addWithId( domainId, (doc?.docId || 0) + 1, pid, - title, content, owner, tag, hidden, + title, content, owner, tag, meta, ); return result; } static async addWithId( domainId: string, docId: number, pid: string = '', title: string, - content: string, owner: number, tag: string[] = [], hidden = false, + content: string, owner: number, tag: string[] = [], + meta: { difficulty?: number, hidden?: boolean } = {}, ) { const args: Partial = { - title, tag, hidden, nSubmit: 0, nAccept: 0, sort: sortable(pid || `P${docId}`), + title, tag, hidden: meta.hidden || false, nSubmit: 0, nAccept: 0, sort: sortable(pid || `P${docId}`), }; if (pid) args.pid = pid; await bus.parallel('problem/before-add', domainId, content, owner, docId, args); @@ -230,7 +231,7 @@ export class ProblemModel { if (!pid && original.pid && !await ProblemModel.get(target, original.pid)) pid = original.pid; const docId = await ProblemModel.add( target, pid, original.title, original.content, - original.owner, original.tag, original.hidden, + original.owner, original.tag, { hidden: original.hidden }, ); await ProblemModel.edit(target, docId, { reference: { domainId, pid: _id } }); return docId; @@ -456,9 +457,10 @@ export class ProblemModel { if (!await isValidPid(pid)) pid = undefined; } const overrideContent = findOverrideContent(path.join(tmpdir, i)); + if (pdoc.difficulty && !Number.isSafeInteger(pdoc.difficulty)) delete pdoc.difficulty; const docId = await ProblemModel.add( domainId, pid, pdoc.title.trim(), overrideContent || pdoc.content || 'No content', - operator || pdoc.owner, pdoc.tag || [], pdoc.hidden, + operator || pdoc.owner, pdoc.tag || [], { hidden: pdoc.hidden, difficulty: pdoc.difficulty }, ); if (files.includes('testdata')) { const datas = await fs.readdir(path.join(tmpdir, i, 'testdata'), { withFileTypes: true }); @@ -503,6 +505,7 @@ export class ProblemModel { tag: pdoc.tag, nSubmit: pdoc.nSubmit, nAccept: pdoc.nAccept, + difficulty: pdoc.difficulty, }); await fs.writeFile(problemYaml, problemYamlContent); try { diff --git a/packages/hydrooj/src/service/decorators.ts b/packages/hydrooj/src/service/decorators.ts index 2e8792e8f..40ff843a3 100644 --- a/packages/hydrooj/src/service/decorators.ts +++ b/packages/hydrooj/src/service/decorators.ts @@ -99,6 +99,15 @@ export const subscribe: (name: keyof EventMap) => MethodDecorator & ClassDecorat }; }; +/* + * For security concern, some API requires sudo privilege to access. + * And for some superadmin operations, + * we do not allow them using a password to perform the sudo operation, + * as most user choose to use "remember password" option. + * When teachers are using a superuser account, accessing from classrooms, + * it may lead to serious security issues. + * !!! Please make sure that all superuser accounts have two factor authentication enabled. !!! + */ export function requireSudo(target: any, funcName: string, obj: any) { const originalMethod = obj.value; obj.value = function sudo(this: Handler, ...args: any[]) { diff --git a/packages/migrate/scripts/hustoj.ts b/packages/migrate/scripts/hustoj.ts index 684b107f4..73b342634 100644 --- a/packages/migrate/scripts/hustoj.ts +++ b/packages/migrate/scripts/hustoj.ts @@ -182,7 +182,8 @@ export async function run({ const pid = await ProblemModel.add( domainId, `P${pdoc.problem_id}`, pdoc.title, content, - 1, pdoc.source.split(' ').map((i) => i.trim()).filter((i) => i), pdoc.defunct === 'Y', + 1, pdoc.source.split(' ').map((i) => i.trim()).filter((i) => i), + { hidden: pdoc.defunct === 'Y' }, ); pidMap[pdoc.problem_id] = pid; await Promise.all(Object.keys(files).map((filename) => ProblemModel.addAdditionalFile(domainId, pid, filename, files[filename]))); diff --git a/packages/vjudge/src/model.ts b/packages/vjudge/src/model.ts index 70ee4fa52..55b6429f2 100644 --- a/packages/vjudge/src/model.ts +++ b/packages/vjudge/src/model.ts @@ -90,7 +90,7 @@ class AccountService { try { const res = await this.api.getProblem(pid, meta); if (!res) continue; - const docId = await ProblemModel.add(domainId, pid, res.title, res.content, 1, res.tag, false); + const docId = await ProblemModel.add(domainId, pid, res.title, res.content, 1, res.tag); if (res.difficulty) await ProblemModel.edit(domainId, docId, { difficulty: res.difficulty }); for (const key in res.files) { await ProblemModel.addAdditionalFile(domainId, docId, key, res.files[key]);