diff --git a/CHANGELOG.md b/CHANGELOG.md index 748c0c5b..9b2c47ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +1.2.0 / 2018-05-04 +================== + +**General** + +* Updated to Flask 1.0 & switched documentation to suggest using `flask run` instead of `python serve.py`. +* Added the ability to make static & regex flags case insensitive. +* The `/chals` endpoint no longer lists the details of challenges. + * The `/chals/:id` endpoint is now used to load challenge information before display. +* Admins can now see what users have solved a given challenge from the admin panel. +* Fixed issue with imports extracting files outside of the CTFd directory. +* Added import zipfile validation and optional size restriction. +* The ctftime, authentication, and admin restrictions have been converted to decorators to improve code reuse. + * 403 is now a more common status code. Previously it only indicated CSRF failure, now it can indicate login failure + or other Forbidden access situations. +* Challenge previews now work consistently instead of occasionally failing to show. +* Tests are now randomly ordered with `nose-randomly`. + +**Themes** + +* Admins now have the ability to upload a CTF logo from the config panel. +* Switched from the `marked` library to `Markdown-It` for client side markdown rendering. + * This will break Challenge type plugins that override the markdown renderer since we are no longer using the marked renderers. +* Introduced the `ezpg()` JS function to make it easier to draw a progressbar modal. +* Introduced the `$.patch()` AJAX wrapper. +* Team names are truncated properly to 50 characters in `teams.html`. +* The admin panel now uses Bootstrap badges instead of buttons to indicate properties such as `admin`, `verified`, `visible`. + +**Plugins** + +* Challenge type plugins now use a global challenge object with exposed functions to specify how to display a challenge. +(`preRender()`, `render()`, `postRender()`, `submit()`). + * Challenge type plugins also have access to window.challenge.data which allow for the previously mentioned functions to + process challenge data and change logic accordingly. +* Challenge type plugins now get full control over how a challenge is displayed via the nunjucks files. +* Challenge plugins should now pass the entire flag/key object to a Custom flag type. + * This allows the flag type to make use of the data column to decide how to operate on the flag. This is used to implement + case insensitive flags. +* Challenge modals (`modal.njk`) now use `{{ description }}` instead of `{{ desc }}` properly aligning with the database schema. +* The update and create modals now inject data into the modal via nunjucks instead of client side Javascript. +* The `utils.base64decode()` & `utils.base64encode()` functions no longer expose url encoding/decoding parameters. + + 1.1.4 / 2018-04-05 ================== diff --git a/CTFd/__init__.py b/CTFd/__init__.py index e9ade32e..913aa07d 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -17,7 +17,7 @@ if sys.version_info[0] < 3: reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '1.1.4' +__version__ = '1.2.0' class CTFdFlask(Flask): diff --git a/CTFd/admin/__init__.py b/CTFd/admin/__init__.py index a39bed69..78faaa4b 100644 --- a/CTFd/admin/__init__.py +++ b/CTFd/admin/__init__.py @@ -147,8 +147,8 @@ def admin_config(): utils.set_config("mail_username", None) utils.set_config("mail_password", None) - if request.files.get('ctf_logo', None): - ctf_logo = request.files['ctf_logo'] + if request.files.get('ctf_logo_file', None): + ctf_logo = request.files['ctf_logo_file'] file_id, file_loc = utils.upload_file(ctf_logo, None) utils.set_config("ctf_logo", file_loc) elif request.form.get('ctf_logo') == '': diff --git a/CTFd/plugins/challenges/assets/standard-challenge-modal.js b/CTFd/plugins/challenges/assets/standard-challenge-modal.js index f30985df..7f48e0a8 100644 --- a/CTFd/plugins/challenges/assets/standard-challenge-modal.js +++ b/CTFd/plugins/challenges/assets/standard-challenge-modal.js @@ -1,3 +1,5 @@ +window.challenge.data = undefined; + window.challenge.renderer = new markdownit({ html: true, }); diff --git a/CTFd/themes/admin/static/js/challenges/chalboard.js b/CTFd/themes/admin/static/js/challenges/chalboard.js index 7ab07443..3be7989b 100644 --- a/CTFd/themes/admin/static/js/challenges/chalboard.js +++ b/CTFd/themes/admin/static/js/challenges/chalboard.js @@ -37,6 +37,8 @@ function render_challenge_preview(chal_id){ $.get(script_root + obj.type_data.templates.modal, function (template_data) { var template = nunjucks.compile(template_data); + window.challenge.data = obj; + window.challenge.preRender() obj['description'] = window.challenge.render(obj['description']); diff --git a/CTFd/themes/admin/templates/config.html b/CTFd/themes/admin/templates/config.html index fd943e7a..b1d4d6ec 100644 --- a/CTFd/themes/admin/templates/config.html +++ b/CTFd/themes/admin/templates/config.html @@ -55,19 +55,20 @@ {% endif %} - + +
diff --git a/CTFd/themes/core/static/js/chalboard.js b/CTFd/themes/core/static/js/chalboard.js index 78ba2a98..66d7eb33 100644 --- a/CTFd/themes/core/static/js/chalboard.js +++ b/CTFd/themes/core/static/js/chalboard.js @@ -33,6 +33,8 @@ function updateChalWindow(obj) { var nonce = $('#nonce').val(); + window.challenge.data = challenge_data; + window.challenge.preRender(); challenge_data['description'] = window.challenge.render(challenge_data['description']); diff --git a/CTFd/themes/core/templates/scoreboard.html b/CTFd/themes/core/templates/scoreboard.html index 4d48c2c4..e9ceb5bc 100644 --- a/CTFd/themes/core/templates/scoreboard.html +++ b/CTFd/themes/core/templates/scoreboard.html @@ -46,7 +46,7 @@ {% for team in teams %} {{ loop.index }} - {{ team.name }} + {{ team.name | truncate(50) }} {{ team.score }} {% endfor %} diff --git a/CTFd/utils/__init__.py b/CTFd/utils/__init__.py index 246ceae3..5d1da00f 100644 --- a/CTFd/utils/__init__.py +++ b/CTFd/utils/__init__.py @@ -192,7 +192,10 @@ def init_utils(app): @app.before_request def csrf(): - func = app.view_functions[request.endpoint] + try: + func = app.view_functions[request.endpoint] + except KeyError: + abort(404) if hasattr(func, '_bypass_csrf'): return if not session.get('nonce'): diff --git a/README.md b/README.md index ac78ec75..054e4929 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ CTFd is a Capture The Flag framework focusing on ease of use and customizability ## Install 1. Run `./prepare.sh` to install dependencies using apt. 2. Modify [CTFd/config.py](https://github.com/CTFd/CTFd/blob/master/CTFd/config.py) to your liking. - 3. Use `python serve.py` in a terminal to drop into debug mode. + 3. Use `flask run` in a terminal to drop into debug mode. Or you can use Docker with the following command: diff --git a/migrations/versions/dab615389702_update_regex_flags_to_case_insensitive.py b/migrations/versions/dab615389702_update_regex_flags_to_case_insensitive.py new file mode 100644 index 00000000..0637c7fe --- /dev/null +++ b/migrations/versions/dab615389702_update_regex_flags_to_case_insensitive.py @@ -0,0 +1,51 @@ +"""Update regex flags to be case_insensitive + +Revision ID: dab615389702 +Revises: d5a224bf5862 +Create Date: 2018-05-03 18:18:53.343075 + +""" +from CTFd.models import db +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql +from sqlalchemy.sql import text, table, column, and_ + +# revision identifiers, used by Alembic. +revision = 'dab615389702' +down_revision = 'd5a224bf5862' +branch_labels = None +depends_on = None + + +keys_table = table('keys', + column('id', db.Integer), + column('type', db.Text), + column('data', db.Text) +) + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + connection.execute( + keys_table.update().where( + keys_table.c.type == 'regex' + ).values( + data='case_insensitive' + ) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + connection.execute( + keys_table.update().where( + keys_table.c.type == 'regex' + ).values( + data=None + ) + ) + # ### end Alembic commands ### diff --git a/tests/user/test_user_facing.py b/tests/user/test_user_facing.py index a37e5c79..579ab595 100644 --- a/tests/user/test_user_facing.py +++ b/tests/user/test_user_facing.py @@ -20,6 +20,18 @@ def test_index(): destroy_ctfd(app) +def test_not_found(): + """Should return a 404 for pages that are not found""" + app = create_ctfd() + with app.app_context(): + with app.test_client() as client: + r = client.get('/this-should-404') + assert r.status_code == 404 + r = client.post('/this-should-404') + assert r.status_code == 404 + destroy_ctfd(app) + + def test_page(): """Test that users can access pages that are created in the database""" app = create_ctfd()