From 4ec2a46877e822b9427a06a00789a1ec7b81c3a7 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 28 Apr 2020 18:16:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9D=83=E9=99=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/handler/manage.js | 41 ++++--- hydro/handler/user.js | 13 +- hydro/install.js | 2 +- hydro/model/builtin.js | 2 +- hydro/model/user.js | 25 +++- hydro/permission.js | 113 +++++++++++++----- templates/domain_manage_permission.html | 9 +- templates/domain_manage_role.html | 4 +- templates/domain_manage_user.html | 34 ++++-- templates/partials/nav.html | 2 +- .../autocomplete/UserSelectAutoComplete.js | 2 +- 11 files changed, 172 insertions(+), 75 deletions(-) diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 036effdb9..3863df248 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -1,13 +1,11 @@ -const system = require('../model/system'); const user = require('../model/user'); -const builtin = require('../model/builtin'); const { Route, Handler } = require('../service/server'); -const { PERM_ADMIN } = require('../permission'); +const { PERM_MANAGE } = require('../permission'); const { RoleAlreadyExistError, ValidationError } = require('../error'); class ManageHandler extends Handler { async prepare() { - this.checkPerm(PERM_ADMIN); + this.checkPerm(PERM_MANAGE); this.system = await user.getById(0); } } @@ -43,20 +41,22 @@ class ManageEditHandler extends ManageHandler { class ManageUserHandler extends ManageHandler { async get() { const uids = []; - const [rudocs, udocs, roles] = await Promise.all([ - system.get(), - user.getMulti({ role: { $neq: 'default' } }).toArray(), + const rudocs = {}; + const [udocs, roles, system] = await Promise.all([ + user.getMulti({ role: { $ne: 'default' } }).toArray(), user.getRoles(), + user.getById(0), ]); + for (const role of roles) rudocs[role._id] = []; for (const udoc of udocs) { uids.push(udoc._id); rudocs[udoc.role].push(udoc); } - const roles_with_text = roles.map((role) => [role, role]); + const rolesSelect = roles.map((role) => [role._id, role._id]); const udict = await user.getList(uids); this.response.template = 'domain_manage_user.html'; this.response.body = { - roles, roles_with_text, rudocs, udict, + roles, rolesSelect, rudocs, udict, system, }; } @@ -68,9 +68,12 @@ class ManageUserHandler extends ManageHandler { class ManagePermissionHandler extends ManageHandler { async get() { - const roles = await user.getRoles(); + const [roles, system] = await Promise.all([ + user.getRoles(), + user.getById(0), + ]); this.response.template = 'domain_manage_permission.html'; - this.response.body = { roles }; + this.response.body = { roles, system }; } async post({ roles }) { @@ -88,21 +91,27 @@ class ManagePermissionHandler extends ManageHandler { class ManageRoleHandler extends ManageHandler { async get() { - const roles = await user.getRoles(); + const [roles, system] = await Promise.all([ + user.getRoles(), + user.getById(0), + ]); this.response.template = 'domain_manage_role.html'; - this.response.body = { roles }; + this.response.body = { roles, system }; } async postAdd({ role }) { - const r = await user.getRole(role); + const [r, u] = await Promise.all([ + user.getRole(role), + user.getRole('default'), + ]); if (r) throw new RoleAlreadyExistError(role); - await user.addRole(role, builtin.PERM_DEFAULT); + await user.addRole(role, u.perm); this.back(); } async postDelete({ roles }) { for (const role of roles) { - if (['admin', 'default', 'guest'].includes(role)) { + if (['root', 'default', 'guest'].includes(role)) { throw new ValidationError('role'); } } diff --git a/hydro/handler/user.js b/hydro/handler/user.js index 9b5b115db..22cddb993 100644 --- a/hydro/handler/user.js +++ b/hydro/handler/user.js @@ -30,6 +30,7 @@ class UserLoginHandler extends Handler { this.response.redirect = referer.endsWith('/login') ? '/' : referer; } } + class UserLogoutHandler extends Handler { async prepare() { this.checkPerm(PERM_LOGGEDIN); @@ -44,6 +45,7 @@ class UserLogoutHandler extends Handler { this.response.body = {}; } } + class UserRegisterHandler extends Handler { async prepare() { this.checkPerm(PERM_REGISTER_USER); @@ -71,6 +73,7 @@ class UserRegisterHandler extends Handler { } } } + class UserRegisterWithCodeHandler extends Handler { async prepare() { this.checkPerm(PERM_REGISTER_USER); @@ -99,6 +102,7 @@ class UserRegisterWithCodeHandler extends Handler { this.response.redirect = '/'; } } + class UserLostPassHandler extends Handler { constructor(ctx) { if (!options.smtp.user) throw new SystemError('Cannot send mail'); @@ -123,6 +127,7 @@ class UserLostPassHandler extends Handler { this.response.template = 'user_lostpass_mail_sent.html'; } } + class UserLostPassWithCodeHandler extends Handler { async get({ code }) { const tdoc = await token.get(code, token.TYPE_LOSTPASS); @@ -140,6 +145,7 @@ class UserLostPassWithCodeHandler extends Handler { this.response.redirect = '/'; } } + class UserDetailHandler extends Handler { constructor(ctx) { super(ctx); @@ -154,6 +160,7 @@ class UserDetailHandler extends Handler { this.response.body = { isSelfProfile, udoc, sdoc }; } } + class UserSearchHandler extends Handler { async get({ q, exactMatch = false }) { let udocs; @@ -161,7 +168,7 @@ class UserSearchHandler extends Handler { else udocs = await user.getPrefixList(q, 20); try { const udoc = await user.getById(parseInt(q)); - if (udoc) udocs.insert(0, udoc); + if (udoc) udocs.push(udoc); } catch (e) { /* Ignore */ } @@ -170,7 +177,7 @@ class UserSearchHandler extends Handler { udocs[i].gravatar_url = misc.gravatar_url[udocs[i].gravatar]; } } - this.response.body = { udocs }; + this.response.body = udocs; } } @@ -180,5 +187,5 @@ Route('/register/:code', UserRegisterWithCodeHandler); Route('/logout', UserLogoutHandler); Route('/lostpass', UserLostPassHandler); Route('/lostpass/:code', UserLostPassWithCodeHandler); -Route('/user/:uid', UserDetailHandler); Route('/user/search', UserSearchHandler); +Route('/user/:uid', UserDetailHandler); diff --git a/hydro/install.js b/hydro/install.js index fe8ffd21c..2177fe5c7 100644 --- a/hydro/install.js +++ b/hydro/install.js @@ -44,7 +44,7 @@ async function run() { hash: pwhash.hash('rootroot', salt), salt, gravatar: 'root@hydro', - role: 'admin', + role: 'root', }, udoc), ]); } diff --git a/hydro/model/builtin.js b/hydro/model/builtin.js index fe28aaf70..69538f32f 100644 --- a/hydro/model/builtin.js +++ b/hydro/model/builtin.js @@ -3,7 +3,7 @@ const perm = require('../permission'); const BUILTIN_ROLES = [ { _id: 'guest', perm: perm.PERM_BASIC }, { _id: 'default', perm: perm.PERM_DEFAULT, count: 0 }, - { _id: 'admin', perm: perm.PERM_ADMIN, count: 0 }, + { _id: 'root', perm: perm.PERM_ALL, count: 0 }, ]; const CATEGORIES = { 动态规划: [ diff --git a/hydro/model/user.js b/hydro/model/user.js index 5262ffdea..f28adb3fd 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -1,5 +1,6 @@ const system = require('./system'); const { UserNotFoundError, UserAlreadyExistError } = require('../error'); +const perm = require('../permission'); const pwhash = require('../lib/pwhash'); const db = require('../service/db'); @@ -25,7 +26,7 @@ class USER { } hasPerm(perm) { - return this.perm === '-' || (this.perm || '').includes(perm); + return this.perm.includes(perm); } checkPassword(password) { @@ -122,7 +123,7 @@ async function create({ role, gravatar: mail, }), - collRole.updateOne({ _id: role }, { $inc: { count: 1 } }) + collRole.updateOne({ _id: role }, { $inc: { count: 1 } }), ]); } catch (e) { throw new UserAlreadyExistError([uid, uname, mail]); @@ -133,6 +134,13 @@ function getMulti(params) { return coll.find(params); } +async function getPrefixList(prefix, limit = 50) { + prefix = prefix.toLowerCase(); + const $regex = new RegExp(`\\A\\Q${prefix.replace(/\\E/gmi, /\\E\\E\\Q/gmi)}\\E`, 'gmi'); + const udocs = await coll.find({ unameLower: { $regex } }).limit(limit).toArray(); + return udocs; +} + async function setRole(uid, role) { const udoc = await getById(uid); return await Promise.all([ @@ -143,7 +151,7 @@ async function setRole(uid, role) { } function getRoles() { - return collRole.find().toArray(); + return collRole.find().sort('_id', 1).toArray(); } function getRole(name) { @@ -155,7 +163,14 @@ function addRole(name, perm) { } function deleteRoles(roles) { - return collRole.deleteMany({ _id: { $in: roles } }); + return Promise.all([ + coll.updateMany({ role: { $in: roles } }, { $set: { role: 'default' } }), + collRole.deleteMany({ _id: { $in: roles } }), + ]); +} + +function init() { + return collRole.update({ _id: 'root' }, { $set: { perm: perm.PERM_ALL } }); } module.exports = { @@ -169,10 +184,12 @@ module.exports = { setById, setEmail, setPassword, + getPrefixList, setRole, getRole, getList, getRoles, addRole, deleteRoles, + init, }; diff --git a/hydro/permission.js b/hydro/permission.js index 98cfc3440..969e22098 100644 --- a/hydro/permission.js +++ b/hydro/permission.js @@ -1,12 +1,8 @@ -const permissions = { - PERM_NONE: '0', - PERM_ALL: '-', - PERM_LOGGEDIN: '~', - PERM_REGISTER_USER: '+', - PERM_VIEW: '1', - PERM_EDIT_PERM: '2', +const perm = { + PERM_MANAGE: '#', + PERM_LOGGEDIN: '0', + PERM_REGISTER_USER: '1', PERM_MOD_BADGE: '3', - PERM_EDIT_DESCRIPTION: '4', PERM_CREATE_PROBLEM: '5', PERM_EDIT_PROBLEM: '6', PERM_VIEW_PROBLEM: '7', @@ -43,28 +39,79 @@ const permissions = { PERM_EDIT_TRAINING: 'c', PERM_JUDGE: 'd', }; -permissions.PERM_BASIC = permissions.PERM_VIEW - + permissions.PERM_VIEW_PROBLEM - + permissions.PERM_VIEW_PROBLEM_SOLUTION - + permissions.PERM_VIEW_DISCUSSION - + permissions.PERM_VIEW_CONTEST - + permissions.PERM_VIEW_CONTEST_SCOREBOARD - + permissions.PERM_REGISTER_USER; -permissions.PERM_DEFAULT = permissions.PERM_VIEW - + permissions.PERM_VIEW_PROBLEM - + permissions.PERM_SUBMIT_PROBLEM - + permissions.PERM_VIEW_PROBLEM_SOLUTION - + permissions.PERM_CREATE_PROBLEM_SOLUTION - + permissions.PERM_VOTE_PROBLEM_SOLUTION - + permissions.PERM_REPLY_PROBLEM_SOLUTION - + permissions.PERM_VIEW_DISCUSSION - + permissions.PERM_CREATE_DISCUSSION - + permissions.PERM_REPLY_DISCUSSION - + permissions.PERM_VIEW_CONTEST - + permissions.PERM_VIEW_CONTEST_SCOREBOARD - + permissions.PERM_ATTEND_CONTEST - + permissions.PERM_VIEW_TRAINING - + permissions.PERM_CREATE_TRAINING - + permissions.PERM_LOGGEDIN; -permissions.PERM_ADMIN = permissions.PERM_ALL; -module.exports = permissions; + +let PERM_ALL = ''; +for (const p in perm) PERM_ALL += perm[p]; +perm.PERM_ALL = PERM_ALL; + +const Permission = (family, key, desc) => ({ family, key, desc }); + +const PERMS = [ + Permission('perm_general', perm.PERM_REGISTER_USER, 'Register new account'), + Permission('perm_general', perm.PERM_MANAGE, 'Manage the system'), + Permission('perm_general', perm.PERM_MOD_BADGE, 'Show MOD badge'), + Permission('perm_problem', perm.PERM_CREATE_PROBLEM, 'Create problems'), + Permission('perm_problem', perm.PERM_EDIT_PROBLEM, 'Edit problems'), + Permission('perm_problem', perm.PERM_VIEW_PROBLEM, 'View problems'), + Permission('perm_problem', perm.PERM_VIEW_PROBLEM_HIDDEN, 'View hidden problems'), + Permission('perm_problem', perm.PERM_SUBMIT_PROBLEM, 'Submit problem'), + Permission('perm_problem', perm.PERM_READ_PROBLEM_DATA, 'Read data of problem'), + Permission('perm_record', perm.PERM_READ_RECORD_CODE, 'Read record codes'), + Permission('perm_record', perm.PERM_REJUDGE_PROBLEM, 'Rejudge problems'), + Permission('perm_record', perm.PERM_REJUDGE, 'Rejudge records'), + Permission('perm_problem_solution', perm.PERM_VIEW_PROBLEM_SOLUTION, 'View problem solutions'), + Permission('perm_problem_solution', perm.PERM_CREATE_PROBLEM_SOLUTION, 'Create problem solutions'), + Permission('perm_problem_solution', perm.PERM_VOTE_PROBLEM_SOLUTION, 'Vote problem solutions'), + Permission('perm_problem_solution', perm.PERM_EDIT_PROBLEM_SOLUTION, 'Edit problem solutions'), + Permission('perm_problem_solution', perm.PERM_DELETE_PROBLEM_SOLUTION, 'Delete 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_DELETE_PROBLEM_SOLUTION_REPLY, 'Delete problem solution replies'), + Permission('perm_discussion', perm.PERM_VIEW_DISCUSSION, 'View discussions'), + Permission('perm_discussion', perm.PERM_CREATE_DISCUSSION, 'Create discussions'), + Permission('perm_discussion', perm.PERM_HIGHLIGHT_DISCUSSION, 'Highlight discussions'), + Permission('perm_discussion', perm.PERM_EDIT_DISCUSSION, 'Edit discussions'), + Permission('perm_discussion', perm.PERM_DELETE_DISCUSSION, 'Delete 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_DELETE_DISCUSSION_REPLY, 'Delete discussion replies'), + Permission('perm_contest', perm.PERM_VIEW_CONTEST, 'View contests'), + Permission('perm_contest', perm.PERM_VIEW_CONTEST_SCOREBOARD, 'View contest scoreboard'), + Permission('perm_contest', perm.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, 'View hidden contest submission status and scoreboard'), + Permission('perm_contest', perm.PERM_CREATE_CONTEST, 'Create contests'), + Permission('perm_contest', perm.PERM_ATTEND_CONTEST, 'Attend contests'), + Permission('perm_contest', perm.PERM_EDIT_CONTEST, 'Edit any contests'), + Permission('perm_training', perm.PERM_VIEW_TRAINING, 'View training plans'), + Permission('perm_training', perm.PERM_CREATE_TRAINING, 'Create training plans'), + Permission('perm_training', perm.PERM_EDIT_TRAINING, 'Edit training plans'), +]; + +const PERMS_BY_FAMILY = {}; +for (const p of PERMS) { + if (!PERMS_BY_FAMILY[p.family]) PERMS_BY_FAMILY[p.family] = [p]; + else PERMS_BY_FAMILY[p.family].push(p); +} +perm.PERMS_BY_FAMILY = PERMS_BY_FAMILY; + +perm.PERM_BASIC = perm.PERM_VIEW_PROBLEM + + perm.PERM_VIEW_PROBLEM_SOLUTION + + perm.PERM_VIEW_DISCUSSION + + perm.PERM_VIEW_TRAINING + + perm.PERM_VIEW_CONTEST + + perm.PERM_VIEW_CONTEST_SCOREBOARD + + perm.PERM_REGISTER_USER; + +perm.PERM_DEFAULT = perm.PERM_BASIC + + perm.PERM_SUBMIT_PROBLEM + + perm.PERM_CREATE_PROBLEM_SOLUTION + + perm.PERM_VOTE_PROBLEM_SOLUTION + + perm.PERM_REPLY_PROBLEM_SOLUTION + + perm.PERM_CREATE_DISCUSSION + + perm.PERM_REPLY_DISCUSSION + + perm.PERM_ATTEND_CONTEST + + perm.PERM_CREATE_TRAINING + + perm.PERM_LOGGEDIN; + +perm.PERM_ADMIN = perm.PERM_ALL; + +module.exports = perm; diff --git a/templates/domain_manage_permission.html b/templates/domain_manage_permission.html index 92d8d9dc0..91ceccb7b 100644 --- a/templates/domain_manage_permission.html +++ b/templates/domain_manage_permission.html @@ -1,9 +1,10 @@ +{% set page_name = "domain_manage_permission" %} {% extends "manage_base.html" %} {% block domain_content %}
-

{{ _('{0}: Permissions').format(handler.domain['name']) }}

+

{{ _('{0}: Permissions').format(system.uname) }}

@@ -17,11 +18,11 @@ {% for role in roles %} - + {% endfor %} - {% for family, perms in perm.PERMS_BY_FAMILY.items() %} + {% for family, perms in perm.PERMS_BY_FAMILY %} {% endfor %} diff --git a/templates/domain_manage_role.html b/templates/domain_manage_role.html index 5e33115c7..b6fccba74 100644 --- a/templates/domain_manage_role.html +++ b/templates/domain_manage_role.html @@ -21,7 +21,7 @@
-

{{ _('{0}: Roles').format(handler.domain['name']) }}

+

{{ _('{0}: Roles').format(system.uname) }}

@@ -48,7 +48,7 @@
{% for role in roles %} - {% if role._id not in ['default', 'guest', 'admin'] %} + {% if role._id not in ['default', 'guest', 'root'] %} {% for role in roles %} {% for rudoc in rudocs[role] %} - {% set is_disabled=(rudoc['uid'] == handler.domain['owner']) %} - + {% set is_disabled=(rudoc._id == handler.user._id) %} + {% endfor %} diff --git a/templates/partials/nav.html b/templates/partials/nav.html index 313bba9d3..e2228918f 100644 --- a/templates/partials/nav.html +++ b/templates/partials/nav.html @@ -23,7 +23,7 @@ {% if handler.hasPerm(perm.PERM_VIEW_CONTEST) %} {{ nav_item('/c', 'contest_main', 'contest') }} {% endif %} - {% if handler.hasPerm(perm.PERM_EDIT_DESCRIPTION) or handler.hasPerm(perm.PERM_EDIT_PERM) %} + {% if handler.hasPerm(perm.PERM_MANAGE) %} {{ nav_item('/manage', 'domain_manage', 'domain_manage') }} {% endif %} diff --git a/ui/components/autocomplete/UserSelectAutoComplete.js b/ui/components/autocomplete/UserSelectAutoComplete.js index 080e1a731..5c0d118c4 100644 --- a/ui/components/autocomplete/UserSelectAutoComplete.js +++ b/ui/components/autocomplete/UserSelectAutoComplete.js @@ -16,7 +16,7 @@ function renderItem(user) { return tpl`
- +
${user.uname}
{{ _('Permissions') }}{{ role }}{{ role._id }}
@@ -36,7 +37,7 @@ {% for role in roles %}
- {{ rudoc['uid'] }} + {{ rudoc._id }} - {{ user.render_inline(udict[rudoc['uid']], badge=false) }} + {{ user.render_inline(udict[rudoc._id], badge=false) }} - {{ rudoc['display_name']|default('') }} + {{ rudoc.displayName|default('') }} - {{ form.select(roles_with_text, 'role', value=rudoc['role'], extra_class='compact', disabled=is_disabled) }} + {{ form.select({ + options:rolesSelect, + name:'role', + value:rudoc['role'], + extra_class:'compact', + disabled:is_disabled + }) }}