refactor(auth): 管理员判定上移至 gateway,经 Token payload 下发 is_admin
gateway 登录签发 Token 时,账号命中 [Admin] users 白名单即在 payload 写入 is_admin=true(随 RSA 签名保护);dnfs 移除本地 ADMIN_USERS 硬编码, delete_note/update_note 豁免判断改读 payload 的 is_admin 字段。 旧 Token 无此字段按非管理员处理(安全方向)。
This commit is contained in:
1 parent
e9f9860fe1
commit
46c3a197be
3 files changed
+20
-5
No files matched your search
+5
-4
@@ -37,8 +37,9 @@ from pymsg import MsgClient # noqa: E402
|
|||||||
_DBV2_ADDR = '127.0.0.1'
|
_DBV2_ADDR = '127.0.0.1'
|
||||||
_DBV2_PORT = 8005
|
_DBV2_PORT = 8005
|
||||||
|
|
||||||
# 管理员账号:删除/更新节点时豁免所有权校验(保留旧的 'admin' 以兼容历史数据)
|
# 管理员判定已上移至 gateway:登录时命中 [Admin] users 白名单即在 Token payload
|
||||||
ADMIN_USERS = ('admin@qsont.me', 'qing@qsont.me')
|
# 写入 is_admin=true,本服务通过 info.get('is_admin') 读取,不再本地维护名单。
|
||||||
|
# 旧 Token 无此字段,按非管理员处理(安全方向)。
|
||||||
|
|
||||||
def _call_dbv2(payload: dict, timeout: int = 10) -> dict:
|
def _call_dbv2(payload: dict, timeout: int = 10) -> dict:
|
||||||
"""通过 pymsg 短连接调用 dbv2 数据库服务。
|
"""通过 pymsg 短连接调用 dbv2 数据库服务。
|
||||||
@@ -326,7 +327,7 @@ def delete_note():
|
|||||||
|
|
||||||
data = _call_dbv2(select_payload)
|
data = _call_dbv2(select_payload)
|
||||||
notes = data['data']
|
notes = data['data']
|
||||||
if info['user'] not in ADMIN_USERS:
|
if not info.get('is_admin'):
|
||||||
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
||||||
logs.warning('dnfs', f'delete_note 所有权拒绝 id={note_id}', extra={'user': info['user']})
|
logs.warning('dnfs', f'delete_note 所有权拒绝 id={note_id}', extra={'user': info['user']})
|
||||||
return jsonify({"error": 'It\'s not your note'}), 403
|
return jsonify({"error": 'It\'s not your note'}), 403
|
||||||
@@ -401,7 +402,7 @@ def update_note():
|
|||||||
|
|
||||||
data = _call_dbv2(select_payload)
|
data = _call_dbv2(select_payload)
|
||||||
notes = data['data']
|
notes = data['data']
|
||||||
if info['user'] not in ADMIN_USERS:
|
if not info.get('is_admin'):
|
||||||
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
if not len(notes) == 1 or not notes[0]['owner'] == info['user']:
|
||||||
logs.warning('dnfs', f'update_note 所有权拒绝 id={id}', extra={'user': info['user']})
|
logs.warning('dnfs', f'update_note 所有权拒绝 id={id}', extra={'user': info['user']})
|
||||||
return jsonify({"error": 'It\'s not your note'}), 403
|
return jsonify({"error": 'It\'s not your note'}), 403
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ opened = http://127.0.0.1:9200;http://127.0.0.1:8006;
|
|||||||
# Token 有效期(秒),默认 3600 = 1 小时
|
# Token 有效期(秒),默认 3600 = 1 小时
|
||||||
keep_time = 3600
|
keep_time = 3600
|
||||||
|
|
||||||
|
[Admin]
|
||||||
|
# 管理员账号白名单(分号分隔)。登录时命中即在 Token payload 写入 is_admin=true,
|
||||||
|
# 下游服务(dnfs 等)凭该字段豁免所有权校验,无需各自维护名单。
|
||||||
|
# 修改后需重启 gateway 生效;旧 Token 无此字段,按非管理员处理。
|
||||||
|
users = qing@qsont.me
|
||||||
|
|
||||||
[CORS]
|
[CORS]
|
||||||
# 允许跨域的 Origin 白名单(分号分隔)
|
# 允许跨域的 Origin 白名单(分号分隔)
|
||||||
# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填)
|
# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <us/SHA256.hpp>
|
#include <us/SHA256.hpp>
|
||||||
#include <us/Sqlite3.hpp>
|
#include <us/Sqlite3.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -325,6 +326,7 @@ static std::vector<std::string> _OPEN_URL;
|
|||||||
static std::string _DB_PATH;
|
static std::string _DB_PATH;
|
||||||
static int _TOKEN_KEEP_TIME = 3600; // Token 有效期(秒),可由 [Token] keep_time 配置
|
static int _TOKEN_KEEP_TIME = 3600; // Token 有效期(秒),可由 [Token] keep_time 配置
|
||||||
static std::vector<std::string> _CORS_ORIGINS; // CORS 允许的 Origin 白名单,可由 [CORS] allowed_origins 配置
|
static std::vector<std::string> _CORS_ORIGINS; // CORS 允许的 Origin 白名单,可由 [CORS] allowed_origins 配置
|
||||||
|
static std::vector<std::string> _ADMIN_USERS; // 管理员账号白名单,可由 [Admin] users 配置(分号分隔),登录时写入 Token payload 的 is_admin
|
||||||
|
|
||||||
/* instance items */
|
/* instance items */
|
||||||
static std::unique_ptr<HttpServer> _SERVE;
|
static std::unique_ptr<HttpServer> _SERVE;
|
||||||
@@ -498,6 +500,7 @@ int main(int argc, char **argv) {
|
|||||||
_DB_PATH = cf.get<std::string>("Database.path", "");
|
_DB_PATH = cf.get<std::string>("Database.path", "");
|
||||||
_TOKEN_KEEP_TIME = cf.get<int>("Token.keep_time", 3600);
|
_TOKEN_KEEP_TIME = cf.get<int>("Token.keep_time", 3600);
|
||||||
_CORS_ORIGINS = split(cf.get<std::string>("CORS.allowed_origins", ""), ';');
|
_CORS_ORIGINS = split(cf.get<std::string>("CORS.allowed_origins", ""), ';');
|
||||||
|
_ADMIN_USERS = split(cf.get<std::string>("Admin.users", ""), ';');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -867,10 +870,15 @@ http::response<http::string_body> handleLogin(
|
|||||||
/* 生成令牌 */
|
/* 生成令牌 */
|
||||||
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
|
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
|
||||||
|
|
||||||
|
/* 管理员判定:账号命中 [Admin] users 白名单即在 payload 中签发 is_admin,
|
||||||
|
* 下游服务(dnfs 等)直接读取该字段豁免所有权校验,无需各自维护名单 */
|
||||||
|
bool is_admin = std::find(_ADMIN_USERS.begin(), _ADMIN_USERS.end(), username) != _ADMIN_USERS.end();
|
||||||
|
|
||||||
json::value jv_t = {
|
json::value jv_t = {
|
||||||
{"user", username},
|
{"user", username},
|
||||||
{"created_at", LocalTime::getCurrent()},
|
{"created_at", LocalTime::getCurrent()},
|
||||||
{"keep_time", _TOKEN_KEEP_TIME}
|
{"keep_time", _TOKEN_KEEP_TIME},
|
||||||
|
{"is_admin", is_admin}
|
||||||
};
|
};
|
||||||
std::string data = json::serialize(jv_t);
|
std::string data = json::serialize(jv_t);
|
||||||
std::string auth = Base64::base64_encode(data);
|
std::string auth = Base64::base64_encode(data);
|
||||||
|
|||||||
Reference in new issue
Block a user