diff --git a/CTFd/plugins/__init__.py b/CTFd/plugins/__init__.py index 39508e89..fd8b181b 100644 --- a/CTFd/plugins/__init__.py +++ b/CTFd/plugins/__init__.py @@ -2,8 +2,60 @@ import glob import importlib import os +from flask.helpers import safe_join +from flask import send_file, send_from_directory, abort +from CTFd.utils import admins_only as admins_only_wrapper + + +def register_plugin_assets_directory(app, base_path, admins_only=False): + """ + Registers a directory to serve assets + + :param app: A CTFd application + :param string base_path: The path to the directory + :param boolean admins_only: Whether or not the assets served out of the directory should be accessible to the public + :return: + """ + base_path = base_path.strip('/') + + def assets_handler(path): + return send_from_directory(base_path, path) + + if admins_only: + asset_handler = admins_only_wrapper(assets_handler) + + rule = '/' + base_path + '/' + app.add_url_rule(rule=rule, endpoint=base_path, view_func=assets_handler) + + +def register_plugin_asset(app, asset_path, admins_only=False): + """ + Registers an file path to be served by CTFd + + :param app: A CTFd application + :param string asset_path: The path to the asset file + :param boolean admins_only: Whether or not this file should be accessible to the public + :return: + """ + asset_path = asset_path.strip('/') + + def asset_handler(): + return send_file(asset_path) + + if admins_only: + asset_handler = admins_only_wrapper(asset_handler) + rule = '/' + asset_path + app.add_url_rule(rule=rule, endpoint=asset_path, view_func=asset_handler) + def init_plugins(app): + """ + Searches for the load function in modules in the CTFd/plugins folder. This function is called with the current CTFd + app as a parameter. This allows CTFd plugins to modify CTFd's behavior. + + :param app: A CTFd application + :return: + """ modules = glob.glob(os.path.dirname(__file__) + "/*") blacklist = {'keys', 'challenges', '__pycache__'} for module in modules: diff --git a/tests/helpers.py b/tests/helpers.py index c77943cb..d96d9d37 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -17,19 +17,24 @@ def create_ctfd(ctf_name="CTFd", name="admin", email="admin@ctfd.io", password=" app = create_app('CTFd.config.TestingConfig') if setup: - with app.app_context(): - with app.test_client() as client: - data = {} - r = client.get('/setup') # Populate session with nonce - with client.session_transaction() as sess: - data = { - "ctf_name": ctf_name, - "name": name, - "email": email, - "password": password, - "nonce": sess.get('nonce') - } - client.post('/setup', data=data) + app = setup_ctfd(app, ctf_name, name, email, password) + return app + + +def setup_ctfd(app, ctf_name="CTFd", name="admin", email="admin@ctfd.io", password="password"): + with app.app_context(): + with app.test_client() as client: + data = {} + r = client.get('/setup') # Populate session with nonce + with client.session_transaction() as sess: + data = { + "ctf_name": ctf_name, + "name": name, + "email": email, + "password": password, + "nonce": sess.get('nonce') + } + client.post('/setup', data=data) return app diff --git a/tests/test_plugin_utils.py b/tests/test_plugin_utils.py new file mode 100644 index 00000000..63362b8d --- /dev/null +++ b/tests/test_plugin_utils.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from tests.helpers import * +from CTFd.models import ip2long, long2ip +from CTFd.plugins import register_plugin_assets_directory, register_plugin_asset +from freezegun import freeze_time +from mock import patch +import json +import six + + +def test_register_plugin_asset(): + """Test that plugin asset registration works""" + app = create_ctfd(setup=False) + register_plugin_asset(app, asset_path='/plugins/__init__.py') + app = setup_ctfd(app) + with app.app_context(): + with app.test_client() as client: + r = client.get('/plugins/__init__.py') + assert len(r.get_data(as_text=True)) > 0 + assert r.status_code == 200 + destroy_ctfd(app) + + +def test_register_plugin_assets_directory(): + """Test that plugin asset directory registration works""" + app = create_ctfd(setup=False) + register_plugin_assets_directory(app, base_path='/plugins/') + app = setup_ctfd(app) + with app.app_context(): + with app.test_client() as client: + r = client.get('/plugins/__init__.py') + assert len(r.get_data(as_text=True)) > 0 + assert r.status_code == 200 + r = client.get('/plugins/challenges/__init__.py') + assert len(r.get_data(as_text=True)) > 0 + assert r.status_code == 200 + destroy_ctfd(app)