import os import time import math import json import jwt from flask import Blueprint, request, jsonify, make_response, g, abort from bcrypt import hashpw, gensalt, checkpw from functools import wraps from utils import pgclass, setting_loader, logger, dbhelper, ighelper from utils.misc import error, internal_json2protobuf from utils.platform_consts import PLIST, PLIST_ROOT from protobuf_files import niming_pb2 admin = Blueprint("admin", __name__) # jwt = {"id":user.id, "user":user.user, "exp":time.time} # auth decorator def role_required(permreq: list): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): # get data 嘗試解碼jwt key = os.getenv("JWT_KEY", None) jwtsession = request.cookies.get("token", None) if jwtsession == None: return error("You do not have permission to view this page."), 401 jwtsession = str(jwtsession) try: jwtdata = jwt.decode(jwt = jwtsession, key = key, algorithms = ["HS256"]) except jwt.exceptions.ExpiredSignatureError: return error("Token expired!"), 401 except jwt.exceptions.DecodeError: return error("Invalid token!"), 401 if "id" not in jwtdata or "user" not in jwtdata: return error("Invalid token!"), 401 # db 驗證帳號是否正確 table = pgclass.SQLuser with dbhelper.db.getsession() as session: res = session.query(table).filter(table.user == jwtdata["user"], table.id == jwtdata["id"]).first() if res is None: return error("You do not have permission to view this page."), 401 # permission check 確保用戶有此路徑要求的權限 並且權限名稱皆合法 permissionList = list(set(res.permission)) for p in permissionList: # 檢查用戶JWT是否有不合法的權限名稱 if p not in PLIST_ROOT: return error("The user has invalid permission."), 402 for p in list(set(permreq)): if p not in permissionList: return error("You do not have permission to view this page."), 402 # return g.opuser = res return f(*args, **kwargs) return decorated_function return decorator # login @admin.route("/login", methods=["POST"]) def login(): # args if "username" not in request.json or "password" not in request.json: return error("Arguments error"), 400 username = str(request.json["username"]) password = str(request.json["password"]) # variables settings = setting_loader.loadset() exptime = int(settings["JWT_Valid_Time"]) # db table = pgclass.SQLuser with dbhelper.db.getsession() as session: u = session.query(table).filter(table.user==username).first() # auth if u is None: return error("Login Failed"), 401 # 找不到用戶 if not checkpw(password.encode("utf-8"), u.password.encode("utf-8")): return error("Login Failed"), 401 # 密碼沒法跟hash對上 # jwt key = os.getenv("JWT_KEY", None) if key is None: return error("JWT_KEY error"), 500 jwtdata = {"id": u.id, "user":username, "exp":int(math.floor(time.time() + exptime))} jwtdata = jwt.encode(payload = jwtdata, key = str(key), algorithm = "HS256") # logger logger.logger("login", "User:%s logined"%username) # cookie r = make_response("Access Granted") r.set_cookie("token", jwtdata) return r, 200 @admin.route("me", methods=["GET"]) @role_required([]) def user_me(): opuser = g.opuser return jsonify({"id":opuser.id, "user":opuser.user, "permission":opuser.permission}), 200 #################### # User Area # #################### # list / get / add / delete @admin.route("/user/list", methods={"GET"}) @role_required([]) def user_list(): table = pgclass.SQLuser with dbhelper.db.getsession() as session: users = session.query(table).all() res = [ {"id":u.id, "user":u.user, "permission":u.permission} for u in users ] return jsonify(res), 200 @admin.route("/user/", methods=["GET"]) @role_required([]) def user_get(id:int): table = pgclass.SQLuser with dbhelper.db.getsession() as session: u = session.query(table).filter(table.id==int(id)).first() if u is None: return error("User not found"), 404 return jsonify({"id":u.id, "user":u.user, "permission":u.permission}), 200 @admin.route("/user/", methods=["DELETE"]) @role_required(["usermgr"]) def user_del(id:int): # db table = pgclass.SQLuser with dbhelper.db.getsession() as session: opuser = g.opuser # user who requested # check root tguser = session.query(table).filter(table.id==int(id)).first() if tguser is None: return error("User is not exist"), 400 if tguser.user == "root": return error("You cannot delete user:root"), 400 # delete session.delete(tguser) session.commit() logger.logger("user.delete", "User:%s deleted an user:%s"%(opuser.user, tguser.user)) # logger return jsonify({"result":"OK"}), 200 @admin.route("/user", methods=["POST"]) @role_required(["usermgr"]) def user_add(): # db table = pgclass.SQLuser with dbhelper.db.getsession() as session: # user who requested opuser = g.opuser # payload if "username" not in request.json or "password" not in request.json or \ "permission" not in request.json or not(isinstance(request.json["permission"], list)): return error("Arguments error"), 400 username = str(request.json["username"]) password = str(request.json["password"]) permission = list(set([ str(p) for p in list(request.json["permission"]) ])) # check username and password if username == None or len(username) == 0 or password is None or len(password) == 0: return error("Invalid Username or Password!"), 400 # check permission list for p in permission: if p not in PLIST: return error("Invalid Permission"), 400 # 如果添加的權限名稱不合法 if p not in opuser.permission: return error("You don't have the permission: %s"%p), 402 # 如果用戶本身不具有相同權限 # add users = session.query(table).filter(table.user==username).first() if users is None: # check whether the user already exist pwhash = hashpw(password.encode("utf-8"), gensalt()).decode("utf-8") session.add(table(user=username, password=pwhash, permission=permission)) session.commit() logger.logger("user.create", "User:%s created a new user:%s"%(opuser.user, username)) # logger return jsonify({"user":username, "permission":permission}), 200 else: return error("User already exist!"), 400 #################### # Article Area # #################### # list / get / pend / delete / fileget @admin.route("/article/file/", methods = ["GET"]) @role_required(["article.read"]) def article_fileget(fnhash:str): resp, code = dbhelper.solo_file_fetcher("admin", fnhash) return resp, code @admin.route('/article/list', methods = ["GET"]) @role_required(["article.read"]) def article_list(): res, code = dbhelper.multi_article_fetcher("admin", request.args.get("page"), 80) return res, code def check_key(type:str, key:str | int) -> str | int: if type == 'a': if not (len(key) > 0 and key.isdigit()): return abort(400) outkey = int(key) # id elif type == 'c': if not (len(key) > 0): return abort(400) outkey = str(key) # sha1 else: return abort(404) return outkey # get article / comment @admin.route("/article//", methods=["GET"]) @role_required(["article.read"]) def article_read(type:str, key:str): key = check_key(type, key) if type == 'a': res, code = dbhelper.solo_article_fetcher("admin", key) elif type == 'c': res, code = dbhelper.solo_comment_fetcher("admin", key) if code == 200: return internal_json2protobuf(role="admin", otype=type, original=[res]), code return abort(code) # delete article / comment @admin.route("/article//", methods=["DELETE"]) @role_required(["article.del"]) def article_del(type:str, key:str): key = check_key(type, key) opuser = g.opuser if type == 'a': rtype = niming_pb2.AdminFetchPostResponse result, code = dbhelper.solo_article_remover(role="admin", id=key, opuser=opuser.user) elif type == 'c': rtype = niming_pb2.AdminFetchCommentResponse result, code = dbhelper.solo_comment_remover(role="admin", sha1=key, opuser=opuser.user) if not code == 200: # Exception return abort(code) if type == 'a': appobj = rtype.Message(id=result["id"], mark=result["mark"]) elif type == 'c': appobj = rtype.Message(sha1=result["sha1"], mark=result["mark"]) ret = rtype() ret.posts.append(appobj) return ret.SerializeToString(), 200 # pend article / comment @admin.route("/article//", methods=["PUT"]) @role_required(["article.pend"]) def article_pend(type:str, key:str): key = check_key(type, key) # 找到本體 if type == 'a': tg, code = dbhelper.solo_article_fetcher(role="admin", key=key) elif type == 'c': tg, code = dbhelper.solo_comment_fetcher(role="admin", key=key) if code != 200: return abort(code) # db mark = pgclass.SQLmark with dbhelper.db.getsession() as session: res = session.query(mark).filter(mark.hash==tg["hash"]).first() if res is None: return abort(404) # 如果文章已經公開 if res.mark == "visible": return abort(400) elif res.mark == "pending": res.mark = "visible" session.commit() # run IG Post if type == 'a': result, err = ighelper.request_upload(tg["id"]) if err or result["result"] == "Canceled delete post request": return abort(500) return "OK", 200 else: return abort(500) #################### # Setting Area # #################### # get / set @admin.route("/setting", methods=["GET"]) @role_required(["setting.edit"]) def setting_get(): return jsonify(setting_loader.loadset()), 200 @admin.route("/setting", methods=["POST"]) @role_required(["setting.edit"]) def setting_edit(): opuser = g.opuser.user req = request.json d = None for r in req: d = setting_loader.writeset(r, req.get(r)) if d == 0: return error("Failed"), 401 logger.logger("setting.modify", "User:%s modified settings: %s"%(opuser, json.dumps(request.json))) return jsonify(d), 200 #################### # IGAPI Area # #################### @admin.route("/ig/accinfo", methods=["GET"]) @role_required(["ig.accinfo"]) def ig_accinfo(): result, err = ighelper.request_account_info() if err: return jsonify(result), 500 else: return jsonify(result), 200 @admin.route("/ig/login", methods=["GET"]) @role_required(["ig.login"]) def ig_login(): result, err = ighelper.request_login() if err: return jsonify(result), 500 else: return jsonify(result), 200 @admin.route("/ig/queue", methods=["GET"]) @role_required(["ig.queue"]) def ig_queue(): result = ighelper.request_queue() return jsonify(result), 200