diff --git a/api/controllers/console/__init__.py b/api/controllers/console/__init__.py index 4053cb9ef3..6834d3a0c5 100644 --- a/api/controllers/console/__init__.py +++ b/api/controllers/console/__init__.py @@ -6,7 +6,7 @@ bp = Blueprint('console', __name__, url_prefix='/console/api') api = ExternalApi(bp) # Import other controllers -from . import setup, version, apikey +from . import setup, version, apikey, admin # Import app controllers from .app import app, site, completion, model_config, statistic, conversation, message diff --git a/api/controllers/console/admin.py b/api/controllers/console/admin.py new file mode 100644 index 0000000000..b8166f2b50 --- /dev/null +++ b/api/controllers/console/admin.py @@ -0,0 +1,125 @@ +import os +from functools import wraps + +from flask import request +from flask_restful import Resource, reqparse +from werkzeug.exceptions import NotFound, Unauthorized + +from controllers.console import api +from controllers.console.wraps import only_edition_cloud +from extensions.ext_database import db +from models.model import RecommendedApp, App, InstalledApp + + +def admin_required(view): + @wraps(view) + def decorated(*args, **kwargs): + if not os.getenv('ADMIN_API_KEY'): + raise Unauthorized('API key is invalid.') + + auth_header = request.headers.get('Authorization') + if auth_header is None: + raise Unauthorized('Authorization header is missing.') + + if ' ' not in auth_header: + raise Unauthorized('Invalid Authorization header format. Expected \'Bearer \' format.') + + auth_scheme, auth_token = auth_header.split(None, 1) + auth_scheme = auth_scheme.lower() + + if auth_scheme != 'bearer': + raise Unauthorized('Invalid Authorization header format. Expected \'Bearer \' format.') + + if os.getenv('ADMIN_API_KEY') != auth_token: + raise Unauthorized('API key is invalid.') + + return view(*args, **kwargs) + + return decorated + + +class InsertExploreAppListApi(Resource): + @only_edition_cloud + @admin_required + def post(self): + parser = reqparse.RequestParser() + parser.add_argument('app_id', type=str, required=True, nullable=False, location='json') + parser.add_argument('desc_en', type=str, required=True, nullable=False, location='json') + parser.add_argument('desc_zh', type=str, required=True, nullable=False, location='json') + parser.add_argument('copyright', type=str, required=True, nullable=False, location='json') + parser.add_argument('privacy_policy', type=str, required=True, nullable=False, location='json') + parser.add_argument('category', type=str, required=True, nullable=False, location='json') + parser.add_argument('position', type=int, required=True, nullable=False, location='json') + args = parser.parse_args() + + app = App.query.filter(App.id == args['app_id']).first() + if not app: + raise NotFound('App not found') + + recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == args['app_id']).first() + + if not recommended_app: + recommended_app = RecommendedApp( + app_id=app.id, + description={ + 'en': args['desc_en'], + 'zh': args['desc_zh'] + }, + copyright=args['copyright'], + privacy_policy=args['privacy_policy'], + category=args['category'], + position=args['position'] + ) + + db.session.add(recommended_app) + + app.is_public = True + db.session.commit() + + return {'result': 'success'}, 201 + else: + recommended_app.description = { + 'en': args['desc_en'], + 'zh': args['desc_zh'] + } + + recommended_app.copyright = args['copyright'] + recommended_app.privacy_policy = args['privacy_policy'] + recommended_app.category = args['category'] + recommended_app.position = args['position'] + + app.is_public = True + + db.session.commit() + + return {'result': 'success'}, 200 + + +class InsertExploreAppApi(Resource): + @only_edition_cloud + @admin_required + def delete(self, app_id): + recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == str(app_id)).first() + if not recommended_app: + return {'result': 'success'}, 204 + + app = App.query.filter(App.id == recommended_app.app_id).first() + if app: + app.is_public = False + + installed_apps = InstalledApp.query.filter( + InstalledApp.app_id == recommended_app.app_id, + InstalledApp.tenant_id != InstalledApp.app_owner_tenant_id + ).all() + + for installed_app in installed_apps: + db.session.delete(installed_app) + + db.session.delete(recommended_app) + db.session.commit() + + return {'result': 'success'}, 204 + + +api.add_resource(InsertExploreAppListApi, '/admin/insert-explore-apps') +api.add_resource(InsertExploreAppApi, '/admin/insert-explore-apps/') diff --git a/api/controllers/web/wraps.py b/api/controllers/web/wraps.py index d227a9659e..c68b8f1cf2 100644 --- a/api/controllers/web/wraps.py +++ b/api/controllers/web/wraps.py @@ -42,13 +42,16 @@ def validate_and_get_site(): """ auth_header = request.headers.get('Authorization') if auth_header is None: - raise Unauthorized() + raise Unauthorized('Authorization header is missing.') + + if ' ' not in auth_header: + raise Unauthorized('Invalid Authorization header format. Expected \'Bearer \' format.') auth_scheme, auth_token = auth_header.split(None, 1) auth_scheme = auth_scheme.lower() if auth_scheme != 'bearer': - raise Unauthorized() + raise Unauthorized('Invalid Authorization header format. Expected \'Bearer \' format.') site = db.session.query(Site).filter( Site.code == auth_token,