[0.2.7.4][ci]
All checks were successful
CI / docker-ci (push) Successful in 24s

This commit is contained in:
DSQ
2026-03-17 22:45:56 +08:00
parent 85dd7bc991
commit 71a0723a74
11 changed files with 1191 additions and 75 deletions

View File

@@ -489,6 +489,8 @@ def update_user_by_id_view(request, user_id):
new_username = (payload.get("username") or "").strip()
new_permission = payload.get("permission")
new_password = (payload.get("password") or "").strip()
raw_keys = payload.get("key", None)
raw_manage_keys = payload.get("manage_key", None)
if new_password and len(new_password) < 6:
return JsonResponse({"status": "error", "message": "密码长度至少为6位"}, status=400)
@@ -498,6 +500,28 @@ def update_user_by_id_view(request, user_id):
requester_mgr = set(requester.get("manage_key") or [])
target_keys = set(target.get("key") or [])
def normalize_keys(v):
if v is None:
return None
if isinstance(v, list):
parts = v
elif isinstance(v, str):
parts = re.split(r"[,;;、\r\n]+", v)
else:
parts = [v]
out = []
seen = set()
for p in parts:
s = str(p).strip().strip(";")
if not s or s in seen:
continue
seen.add(s)
out.append(s)
return out
new_keys = normalize_keys(raw_keys)
new_manage_keys = normalize_keys(raw_manage_keys)
if is_admin:
if new_username:
other = get_user_by_username(new_username)
@@ -508,6 +532,8 @@ def update_user_by_id_view(request, user_id):
username=new_username if new_username else None,
permission=int(new_permission) if new_permission is not None else None,
password=new_password if new_password else None,
key=new_keys,
manage_key=new_manage_keys,
)
return JsonResponse({"status": "success"}) if ok else JsonResponse({"status": "error", "message": "用户更新失败"}, status=500)
@@ -518,9 +544,25 @@ def update_user_by_id_view(request, user_id):
return JsonResponse({"status": "success"}) if ok else JsonResponse({"status": "error", "message": "用户更新失败"}, status=500)
if requester_mgr and (target_keys & requester_mgr):
if not new_password or new_username or new_permission is not None:
return JsonResponse({"status": "error", "message": "导师仅允许修改密码"}, status=403)
ok = es_update_user_by_id(user_id, password=new_password)
if new_username or new_permission is not None or new_manage_keys is not None:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
if not new_password and new_keys is None:
return JsonResponse({"status": "error", "message": "缺少更新内容"}, status=400)
merged_keys = None
if new_keys is not None:
try:
new_keys_set = set(new_keys)
except Exception:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
requester_locked = set(normalize_keys(requester.get("key")) or [])
if new_keys_set & requester_locked:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
existing_keys = [str(v).strip() for v in list(target.get("key") or []) if str(v).strip()]
preserved = [k for k in existing_keys if k in requester_locked]
merged_keys = preserved + [str(v).strip() for v in list(new_keys) if str(v).strip() and str(v).strip() not in requester_locked]
seen = set()
merged_keys = [k for k in merged_keys if not (k in seen or seen.add(k))]
ok = es_update_user_by_id(user_id, password=new_password if new_password else None, key=merged_keys)
return JsonResponse({"status": "success"}) if ok else JsonResponse({"status": "error", "message": "用户更新失败"}, status=500)
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
@@ -1019,17 +1061,39 @@ def analytics_recent_view(request):
@require_http_methods(["POST"])
@csrf_protect
def remove_key_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
is_admin = int(request.session.get("permission", 1)) == 0
try:
payload = json.loads(request.body.decode("utf-8"))
key_to_remove = payload.get("key")
if not key_to_remove:
return JsonResponse({"status": "error", "message": "缺少key参数"}, status=400)
key_to_remove = str(key_to_remove).strip()
if not key_to_remove:
return JsonResponse({"status": "error", "message": "缺少key参数"}, status=400)
if not is_admin:
me = get_user_by_id(request.session.get("user_id")) or {}
if not (me.get("manage_key") or []):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
allowed = {str(x).strip() for x in list(request.session.get("tutor_added_manage_keys") or []) if str(x).strip()}
if key_to_remove not in allowed:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
from .es_connect import delete_key_globally
ok, count = delete_key_globally(key_to_remove)
if ok:
if not is_admin:
cur = [str(x).strip() for x in list(request.session.get("tutor_added_manage_keys") or []) if str(x).strip()]
cur = [k for k in cur if k != key_to_remove]
request.session["tutor_added_manage_keys"] = cur
try:
request.session.modified = True
except Exception:
pass
return JsonResponse({"status": "success", "message": f"已成功全局删除 Key '{key_to_remove}',并同步清理了 {count} 个注册码。"})
else:
return JsonResponse({"status": "error", "message": "删除失败"}, status=500)
@@ -1039,6 +1103,35 @@ def remove_key_view(request):
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@require_http_methods(["POST"])
@csrf_protect
def unallow_tutor_added_key_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) == 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
me = get_user_by_id(request.session.get("user_id")) or {}
if not (me.get("manage_key") or []):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
try:
payload = json.loads(request.body.decode("utf-8"))
except Exception:
return JsonResponse({"status": "error", "message": "JSON无效"}, status=400)
key_name = (payload.get("key") or "").strip()
if not key_name:
return JsonResponse({"status": "error", "message": "key不能为空"}, status=400)
cur = list(request.session.get("tutor_added_manage_keys") or [])
cur = [str(x).strip() for x in cur if str(x).strip()]
if key_name not in cur:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
cur = [k for k in cur if k != key_name]
request.session["tutor_added_manage_keys"] = cur
try:
request.session.modified = True
except Exception:
pass
return JsonResponse({"status": "success"})
@require_http_methods(["GET"])
@ensure_csrf_cookie
def user_manage(request):
@@ -1049,6 +1142,15 @@ def user_manage(request):
is_admin = int(request.session.get("permission", 1)) == 0
me = get_user_by_id(session_user_id) or {}
has_manage = bool(me.get("manage_key"))
manage_keys = list(me.get("manage_key") or [])
raw_my_keys = me.get("key") or []
if isinstance(raw_my_keys, list):
my_keys = raw_my_keys
elif isinstance(raw_my_keys, str):
my_keys = re.split(r"[,;;、\r\n]+", raw_my_keys)
else:
my_keys = [raw_my_keys]
my_keys = [str(x).strip() for x in my_keys if str(x).strip()]
user_id_qs = request.GET.get("user_id")
context = {
"user_id": user_id_qs or session_user_id,
@@ -1056,6 +1158,8 @@ def user_manage(request):
"is_admin": is_admin,
"is_tutor": (not is_admin) and has_manage,
"is_student": (not is_admin) and (not has_manage),
"manage_keys_json": json.dumps(manage_keys, ensure_ascii=False),
"my_keys_json": json.dumps(my_keys, ensure_ascii=False),
}
return render(request, "elastic/users.html", context)
@@ -1066,14 +1170,36 @@ def registration_code_manage_page(request):
if session_user_id is None:
from django.shortcuts import redirect
return redirect("/accounts/login/")
if int(request.session.get("permission", 1)) != 0:
is_admin = int(request.session.get("permission", 1)) == 0
me = get_user_by_id(session_user_id) or {}
has_manage = bool(me.get("manage_key"))
can_manage_reg = int(me.get("can_manage_registration_codes") or 0) == 1
if (not is_admin) and (not has_manage) and (not can_manage_reg):
from django.shortcuts import redirect
return redirect("/main/home/")
user_id_qs = request.GET.get("user_id")
me = get_user_by_id(session_user_id) or {}
raw_my_keys = me.get("key") or []
if isinstance(raw_my_keys, list):
my_keys = raw_my_keys
elif isinstance(raw_my_keys, str):
my_keys = re.split(r"[,;;、\r\n]+", raw_my_keys)
else:
my_keys = [raw_my_keys]
my_keys = [str(x).strip() for x in my_keys if str(x).strip()]
if can_manage_reg and (not has_manage) and (not is_admin):
allowed_manage_keys = [str(x).strip() for x in list(me.get("registration_manage_keys") or []) if str(x).strip()]
else:
allowed_manage_keys = list(request.session.get("tutor_added_manage_keys") or [])
allowed_manage_keys = [str(x).strip() for x in allowed_manage_keys if str(x).strip()]
context = {
"user_id": user_id_qs or session_user_id,
"username": me.get("username"),
"is_admin": is_admin,
"has_manage_key": has_manage,
"can_manage_registration_codes": can_manage_reg,
"my_keys_json": json.dumps(my_keys, ensure_ascii=False),
"manage_keys_json": json.dumps(list(me.get("manage_key") or []), ensure_ascii=False),
"allowed_manage_keys_json": json.dumps(allowed_manage_keys, ensure_ascii=False),
}
return render(request, "elastic/registration_codes.html", context)
@@ -1081,8 +1207,16 @@ def registration_code_manage_page(request):
def get_keys_list_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) != 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
is_admin = int(request.session.get("permission", 1)) == 0
if not is_admin:
me = get_user_by_id(request.session.get("user_id")) or {}
has_manage = bool(me.get("manage_key") or [])
can_manage_reg = int(me.get("can_manage_registration_codes") or 0) == 1
if (not has_manage) and (not can_manage_reg):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
if can_manage_reg and (not has_manage):
lst = [str(x).strip() for x in list(me.get("registration_manage_keys") or []) if str(x).strip()]
return JsonResponse({"status": "success", "data": lst})
lst = get_keys_list()
return JsonResponse({"status": "success", "data": lst})
@@ -1091,8 +1225,13 @@ def get_keys_list_view(request):
def add_key_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) != 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
is_admin = int(request.session.get("permission", 1)) == 0
if not is_admin:
me = get_user_by_id(request.session.get("user_id")) or {}
has_manage = bool(me.get("manage_key") or [])
can_manage_reg = int(me.get("can_manage_registration_codes") or 0) == 1
if (not has_manage) and (not can_manage_reg):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
try:
payload = json.loads(request.body.decode("utf-8"))
except Exception:
@@ -1100,9 +1239,29 @@ def add_key_view(request):
key_name = (payload.get("key") or "").strip()
if not key_name:
return JsonResponse({"status": "error", "message": "key不能为空"}, status=400)
cur_global = set(get_keys_list() or [])
if key_name in cur_global:
return JsonResponse({"status": "error", "message": "key已存在"}, status=409)
ok = ensure_key_in_list(key_name)
if not ok:
return JsonResponse({"status": "error", "message": "key已存在或写入失败"}, status=409)
return JsonResponse({"status": "error", "message": "写入失败"}, status=500)
if not is_admin:
uid = request.session.get("user_id")
if has_manage:
cur = list(request.session.get("tutor_added_manage_keys") or [])
cur = [str(x).strip() for x in cur if str(x).strip()]
if key_name not in cur:
cur.append(key_name)
request.session["tutor_added_manage_keys"] = cur
try:
request.session.modified = True
except Exception:
pass
elif can_manage_reg:
cur = [str(x).strip() for x in list((me or {}).get("registration_manage_keys") or []) if str(x).strip()]
if key_name not in cur:
cur.append(key_name)
es_update_user_by_id(uid, registration_manage_keys=cur)
return JsonResponse({"status": "success"})
@require_http_methods(["POST"])
@@ -1110,19 +1269,89 @@ def add_key_view(request):
def generate_registration_code_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) != 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
uid = request.session.get("user_id")
is_admin = int(request.session.get("permission", 1)) == 0
if not is_admin:
me = get_user_by_id(uid) or {}
has_manage = bool(me.get("manage_key") or [])
can_manage_reg = int(me.get("can_manage_registration_codes") or 0) == 1
if (not has_manage) and (not can_manage_reg):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
try:
payload = json.loads(request.body.decode("utf-8"))
except Exception:
return JsonResponse({"status": "error", "message": "JSON无效"}, status=400)
keys = list(payload.get("keys") or [])
manage_keys = list(payload.get("manage_keys") or [])
if not is_admin:
if has_manage:
my_keys = []
try:
my_keys = [str(x).strip() for x in list((me or {}).get("key") or []) if str(x).strip()]
except Exception:
my_keys = []
allowed_manage = set()
try:
allowed_manage = {str(x).strip() for x in list(request.session.get("tutor_added_manage_keys") or []) if str(x).strip()}
except Exception:
allowed_manage = set()
normalized_keys = []
seen = set()
for v in list(keys or []):
s = str(v).strip()
if not s or s in seen:
continue
seen.add(s)
normalized_keys.append(s)
missing = [k for k in my_keys if k not in seen]
if missing:
return JsonResponse(
{"status": "error", "message": f"必须选择导师原有的 key{''.join(missing)}"},
status=400,
)
keys = normalized_keys
clean_manage = []
manage_seen = set()
for v in list(manage_keys or []):
s = str(v).strip()
if not s or s in manage_seen:
continue
if s not in allowed_manage:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
manage_seen.add(s)
clean_manage.append(s)
manage_keys = clean_manage
else:
allowed = {str(x).strip() for x in list((me or {}).get("registration_manage_keys") or []) if str(x).strip()}
norm_keys = []
seen = set()
for v in list(keys or []):
s = str(v).strip()
if not s or s in seen:
continue
if s not in allowed:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
seen.add(s)
norm_keys.append(s)
if not norm_keys:
return JsonResponse({"status": "error", "message": "至少选择一个 key"}, status=400)
keys = norm_keys
clean_manage = []
manage_seen = set()
for v in list(manage_keys or []):
s = str(v).strip()
if not s or s in manage_seen:
continue
if s not in allowed:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
manage_seen.add(s)
clean_manage.append(s)
manage_keys = clean_manage
try:
days = int(payload.get("expires_in_days", 30))
except Exception:
days = 30
result = generate_registration_code(keys=keys, manage_keys=manage_keys, expires_in_days=days, created_by=request.session.get("user_id"))
result = generate_registration_code(keys=keys, manage_keys=manage_keys, expires_in_days=days, created_by=uid)
if not result:
return JsonResponse({"status": "error", "message": "生成失败"}, status=500)
return JsonResponse({"status": "success", "data": result})
@@ -1131,9 +1360,15 @@ def generate_registration_code_view(request):
def list_registration_codes_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) != 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
uid = request.session.get("user_id")
is_admin = int(request.session.get("permission", 1)) == 0
if not is_admin:
me = get_user_by_id(uid) or {}
if (not (me.get("manage_key") or [])) and (int(me.get("can_manage_registration_codes") or 0) != 1):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
data = list_registration_codes()
if not is_admin:
data = [it for it in (data or []) if str(it.get("created_by")) == str(uid)]
return JsonResponse({"status": "success", "data": data})
@@ -1198,8 +1433,12 @@ def keys_for_filter_view(request):
def revoke_registration_code_view(request):
if request.session.get("user_id") is None:
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
if int(request.session.get("permission", 1)) != 0:
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
uid = request.session.get("user_id")
is_admin = int(request.session.get("permission", 1)) == 0
if not is_admin:
me = get_user_by_id(uid) or {}
if (not (me.get("manage_key") or [])) and (int(me.get("can_manage_registration_codes") or 0) != 1):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
try:
payload = json.loads(request.body.decode("utf-8"))
except Exception:
@@ -1207,6 +1446,10 @@ def revoke_registration_code_view(request):
code = (payload.get("code") or "").strip()
if not code:
return JsonResponse({"status": "error", "message": "缺少code"}, status=400)
if not is_admin:
info = get_registration_code(code)
if not info or str(info.get("created_by")) != str(uid):
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
ok = revoke_registration_code(code)
if not ok:
return JsonResponse({"status": "error", "message": "作废失败"}, status=500)