From 35c8934963ba48b733df3bec911cac1aa67e1430 Mon Sep 17 00:00:00 2001 From: hzhang Date: Fri, 25 Apr 2025 00:39:01 +0100 Subject: [PATCH] add: markdown permission setting improve: template --- api/__init__.py | 12 +++++- api/markdown.py | 18 ++++++++- api/resource.py | 1 - api/setting/markdown/__init__.py | 8 +++- api/setting/markdown/permission.py | 56 ++++++++++++++++++++++++++ api/setting/markdown/template.py | 5 +-- api/tree.py | 38 +++++++++++++---- db/models/MarkdownPermissionSetting.py | 15 +++++++ db/models/MarkdownSetting.py | 5 +-- env_provider.py | 1 - logging_handlers/DatabaseLogHandler.py | 2 - 11 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 api/setting/markdown/permission.py create mode 100644 db/models/MarkdownPermissionSetting.py diff --git a/api/__init__.py b/api/__init__.py index 00e04a4..fdbe4a2 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -79,6 +79,17 @@ def verify_token(token): print(e) return None +def is_user_admin(): + is_admin = False + auth_header = request.headers.get('Authorization') + if auth_header and auth_header.startswith('Bearer'): + token = auth_header.split(" ")[1] + decoded = verify_token(token) + if decoded: + user_roles = decoded.get("resource_access", {}).get(env_provider.KC_CLIENT_ID, {}).get("roles", []) + is_admin = 'admin' in user_roles + return is_admin + def require_auth(roles=[]): def decorator(func): @wraps(func) @@ -158,4 +169,3 @@ def etag_response(f): return resp return response return decorator - diff --git a/api/markdown.py b/api/markdown.py index 9d189db..78a37d5 100644 --- a/api/markdown.py +++ b/api/markdown.py @@ -1,12 +1,15 @@ from flask import Blueprint, request, jsonify from sqlalchemy import or_ from api import limiter -from api import require_auth, etag_response +from api import require_auth, etag_response, verify_token, is_user_admin from contexts.RequestContext import RequestContext from db import get_db from db.models.Markdown import Markdown +from db.models.MarkdownSetting import MarkdownSetting +from db.models.MarkdownPermissionSetting import MarkdownPermissionSetting from events import markdown_created, markdown_updated, markdown_deleted import api +import env_provider import logging logger = logging.getLogger(__name__) @@ -54,10 +57,23 @@ def get_index(path_id): @limiter.limit(api.get_rate_limit) @etag_response def get_markdown(markdown_id): + is_admin = is_user_admin() + with get_db() as session: markdown = session.query(Markdown).get(markdown_id) if markdown is None: return jsonify({"error": "file not found"}), 404 + + if not is_admin and markdown.setting_id is not None: + setting = session.query(MarkdownSetting).get(markdown.setting_id) + if setting and setting.permission_setting_id: + permission_setting = session.query(MarkdownPermissionSetting).get(setting.permission_setting_id) + if permission_setting: + if permission_setting.permission == 'private': + return jsonify({"msg": "permission denied"}), 403 + elif permission_setting.permission == 'protected': + return jsonify({"msg": "permission denied"}), 203 + return jsonify(markdown.to_dict()), 200 @markdown_bp.route('/', methods=['POST']) diff --git a/api/resource.py b/api/resource.py index 6b5d0d7..e39d15d 100644 --- a/api/resource.py +++ b/api/resource.py @@ -1,4 +1,3 @@ -#api/resource.py import api from flask import Blueprint, jsonify, request from contexts.RequestContext import RequestContext diff --git a/api/setting/markdown/__init__.py b/api/setting/markdown/__init__.py index 5b3b97e..8f58603 100644 --- a/api/setting/markdown/__init__.py +++ b/api/setting/markdown/__init__.py @@ -23,7 +23,11 @@ def get_markdown_path(setting_id): def create_markdown_setting(): data = request.json template_setting_id = data.get('template_setting_id') - setting = MarkdownSetting(template_setting_id=template_setting_id) + permission_setting_id = data.get('permission_setting_id') + setting = MarkdownSetting( + template_setting_id=template_setting_id, + permission_setting_id=permission_setting_id + ) try: with get_db() as session: session.add(setting) @@ -42,7 +46,9 @@ def update_markdown_setting(setting_id): if setting is None: return jsonify({"error": "setting not exists"}), 400 template_setting_id = data.get('template_setting_id', setting.template_setting_id) + permission_setting_id = data.get('permission_setting_id', setting.permission_setting_id) setting.template_setting_id = template_setting_id + setting.permission_setting_id = permission_setting_id session.commit() return jsonify(setting.to_dict()), 200 except Exception as e: diff --git a/api/setting/markdown/permission.py b/api/setting/markdown/permission.py new file mode 100644 index 0000000..deb91b8 --- /dev/null +++ b/api/setting/markdown/permission.py @@ -0,0 +1,56 @@ +from flask import jsonify, request +import api +from api import etag_response, limiter, require_auth +from api.setting import setting_bp +from db import get_db +from db.models.MarkdownPermissionSetting import MarkdownPermissionSetting + + +@setting_bp.route('/markdown/permission//', methods=['GET']) +@etag_response +@limiter.limit(api.get_rate_limit) +def get_permission_setting(setting_id): + with get_db() as session: + setting = session.query(MarkdownPermissionSetting).get(setting_id) + if not setting: + return jsonify({}), 204 + return jsonify(setting.to_dict()), 200 + + +@setting_bp.route('/markdown/permission/', methods=['POST']) +@require_auth(roles=['admin']) +def create_permission_setting(): + data = request.json + permission = data.get('permission') + new_setting = MarkdownPermissionSetting(permission=permission) + with get_db() as session: + session.add(new_setting) + session.commit() + return jsonify(new_setting.to_dict()), 201 + + +@setting_bp.route('/markdown/permission/', methods=['PUT', 'PATCH']) +@require_auth(roles=['admin']) +def update_permission_setting(setting_id): + with get_db() as session: + setting = session.get(MarkdownPermissionSetting, setting_id) + if setting is None: + return jsonify({"error": "permission setting not found"}), 404 + data = request.json + if request.method == 'PUT': + setting.permission = data.get('permission') + elif request.method == 'PATCH': + if 'permission' in data: + setting.permission = data.get('permission') + session.commit() + return jsonify(setting.to_dict()), 200 + +@setting_bp.route('/markdown/permission/', methods=['DELETE']) +@require_auth(roles=['admin']) +def delete_permission_setting(setting_id): + with get_db() as session: + setting = session.get(MarkdownPermissionSetting, setting_id) + st = setting.to_dict() + session.delete(setting) + session.commit() + return jsonify(st), 200 diff --git a/api/setting/markdown/template.py b/api/setting/markdown/template.py index 8719b1b..587441b 100644 --- a/api/setting/markdown/template.py +++ b/api/setting/markdown/template.py @@ -59,8 +59,5 @@ def delete_template_setting(setting_id): setting = session.get(MarkdownTemplateSetting, setting_id) st = setting.to_dict() session.delete(setting) + session.commit() return jsonify(st), 200 - - - - diff --git a/api/tree.py b/api/tree.py index e270970..32e8063 100644 --- a/api/tree.py +++ b/api/tree.py @@ -1,27 +1,49 @@ from flask import Blueprint, request, jsonify from sqlalchemy.orm import Session +from sqlalchemy import and_, or_ import api -from api import etag_response +from api import etag_response, verify_token, is_user_admin from db import get_db from db.models.Markdown import Markdown from db.models.Path import Path +from db.models.MarkdownSetting import MarkdownSetting +from db.models.MarkdownPermissionSetting import MarkdownPermissionSetting from api import limiter +import env_provider import logging logger = logging.getLogger(__name__) tree_bp = Blueprint('tree', __name__, url_prefix='/api/tree') -def build_tree(db: Session, parent_id: int = None): +def build_tree(db: Session, parent_id: int = None, is_admin=False): path_nodes = db.query(Path).filter(Path.parent_id == parent_id).all() - md_nodes = db.query( + + markdown_query = db.query( Markdown.id, Markdown.title, Markdown.order, Markdown.shortcut, Markdown.setting_id - ).filter(Markdown.path_id == parent_id).all() + ).filter(Markdown.path_id == parent_id) + + if not is_admin: + markdown_query = markdown_query.outerjoin( + MarkdownSetting, + Markdown.setting_id == MarkdownSetting.id + ).outerjoin( + MarkdownPermissionSetting, + MarkdownSetting.permission_setting_id == MarkdownPermissionSetting.id + ).filter( + or_( + MarkdownPermissionSetting.permission != 'private', + MarkdownPermissionSetting.permission == None + ) + ) + + md_nodes = markdown_query.all() + t0 = [ { "id": node.id, @@ -32,7 +54,7 @@ def build_tree(db: Session, parent_id: int = None): } for node in md_nodes ] t1 = [ - {**node.to_dict(), "type": "path", "children": build_tree(db, node.id)} for node in path_nodes + {**node.to_dict(), "type": "path", "children": build_tree(db, node.id, is_admin)} for node in path_nodes ] for node in t1: for child in node["children"]: @@ -45,8 +67,10 @@ def build_tree(db: Session, parent_id: int = None): @limiter.limit(api.get_rate_limit) @etag_response def get_tree(): + is_admin = is_user_admin() + with get_db() as session: - children = build_tree(session, 1) + children = build_tree(session, 1, is_admin) root = session.query(Path).get(1) return jsonify( @@ -56,4 +80,4 @@ def get_tree(): "index": any("title" in child.keys() and child["title"] == "index" for child in children), "children": children } - ), 200 \ No newline at end of file + ), 200 diff --git a/db/models/MarkdownPermissionSetting.py b/db/models/MarkdownPermissionSetting.py new file mode 100644 index 0000000..31485bc --- /dev/null +++ b/db/models/MarkdownPermissionSetting.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, Integer, ForeignKey, String + +from db.models import Base + + +class MarkdownPermissionSetting(Base): + __tablename__ = 'markdown_permission_setting' + id = Column(Integer, primary_key=True, autoincrement=True) + permission = Column(String(255), nullable=True) + + def to_dict(self): + return { + "id": self.id, + "permission": self.permission, + } \ No newline at end of file diff --git a/db/models/MarkdownSetting.py b/db/models/MarkdownSetting.py index 8517514..dc5a919 100644 --- a/db/models/MarkdownSetting.py +++ b/db/models/MarkdownSetting.py @@ -1,15 +1,14 @@ from sqlalchemy import Column, Integer, ForeignKey - from db.models import Base - class MarkdownSetting(Base): __tablename__ = 'markdown_setting' id = Column(Integer, primary_key=True, autoincrement=True) template_setting_id = Column(Integer, ForeignKey('markdown_template_setting.id'), nullable=True) - + permission_setting_id = Column(Integer, ForeignKey('markdown_permission_setting.id'), nullable=True) def to_dict(self): return { "id": self.id, "template_setting_id": self.template_setting_id, + "permission_setting_id": self.permission_setting_id, } \ No newline at end of file diff --git a/env_provider.py b/env_provider.py index 4edddb1..8881f48 100644 --- a/env_provider.py +++ b/env_provider.py @@ -1,4 +1,3 @@ -#env_provider.py import os from dotenv import load_dotenv diff --git a/logging_handlers/DatabaseLogHandler.py b/logging_handlers/DatabaseLogHandler.py index 5c6d7c3..3c5a753 100644 --- a/logging_handlers/DatabaseLogHandler.py +++ b/logging_handlers/DatabaseLogHandler.py @@ -1,5 +1,3 @@ -#logging_handlers/DatabaseLogHandler.py - import logging from db import get_db from db.models.Log import Log