mirror of https://github.com/JohnHammond/CTFd.git
Mark 2.0.0 (#757)
* Update CHANGELOG * Update README * Upgrade migration script to port visibility settings * Add message about visibility settings and port over visibility settings * Close #758 * Add tests for dynamic value challengesselenium-screenshot-testing
parent
821c5552c1
commit
2bd310b5d9
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -1,21 +1,40 @@
|
||||||
2.0.0 / 2018-11-22
|
2.0.0 / 2018-11-26
|
||||||
==================
|
==================
|
||||||
|
|
||||||
2.0.0 is a significant change.
|
2.0.0 is a *significant*, backwards-incompaitble release.
|
||||||
If you are upgrading from a prior version be sure to make backups before upgrading.
|
|
||||||
|
|
||||||
In addition, 2.0.0 is a backwards-incompatible release. Many plugins will be broken in CTFd 2.0.0, and if you're having
|
Many unofficial plugins will not be supported in CTFd 2.0.0. If you're having trouble updating your plugins
|
||||||
trouble updating your plugins please join [the CTFd Slack](https://slack.ctfd.io/) for help and discussion.
|
please join [the CTFd Slack](https://slack.ctfd.io/) for help and discussion.
|
||||||
|
|
||||||
|
If you are upgrading from a prior version be sure to make backups and have a reversion plan before upgrading.
|
||||||
|
|
||||||
|
* If upgrading from 1.2.0 please make use of the `migrations/1_2_0_upgrade_2_0_0.py` script as follows:
|
||||||
|
1. Make all necessary backups. Backup the database, uploads folder, and source code directory.
|
||||||
|
2. Upgrade the source code directory (i.e. `git pull`) but do not run any updated code yet.
|
||||||
|
3. Set the `DATABASE_URL` in `CTFd/config.py` to point to your existing CTFd database.
|
||||||
|
3. Run the upgrade script from the CTFd root folder i.e. `python migrations/1_2_0_upgrade_2_0_0.py`.
|
||||||
|
* This migration script will attempt to migrate data inside the database to 2.0.0 but it cannot account for every situation.
|
||||||
|
* Examples of situations where you may need to manually migrate data:
|
||||||
|
* Tables/columns created by plugins
|
||||||
|
* Tables/columns created by forks
|
||||||
|
* Using databases which are not officially supported (e.g. sqlite, postgres)
|
||||||
|
4. Setup the rest of CTFd (i.e. config.py), migrate/update any plugins, and run normally.
|
||||||
|
* If upgrading from a version before 1.2.0, please upgrade to 1.2.0 and then continue with the steps above.
|
||||||
|
|
||||||
**General**
|
**General**
|
||||||
|
|
||||||
* Seperation of Teams into Users and Teams.
|
* Seperation of Teams into Users and Teams.
|
||||||
* Integration with MajorLeagueCyber. (https://majorleaguecyber.org)
|
* Use User Mode if you want users to register as themselves and play on their own.
|
||||||
|
* Use Team Mode if you want users to create and join teams to play together.
|
||||||
|
* Integration with MajorLeagueCyber (MLC). (https://majorleaguecyber.org)
|
||||||
|
* Organizers can register their event with MLC and will receive OAuth Client ID & Client Secret.
|
||||||
|
* Organizers can set those OAuth credentials in CTFd to allow users and teams to automatically register in a CTF.
|
||||||
* Data is now provided to the front-end via the REST API. (#551)
|
* Data is now provided to the front-end via the REST API. (#551)
|
||||||
* Javascript uses `fetch()` to consume the REST API.
|
* Javascript uses `fetch()` to consume the REST API.
|
||||||
* Dynamic Challenges built in.
|
* Dynamic Challenges are built in.
|
||||||
* S3 Uploader built in. (#661)
|
* S3 backed uploading/downloading built in. (#661)
|
||||||
* Real time notifications. (#600)
|
* Real time notifications/announcements. (#600)
|
||||||
|
* Uses long-polling instead of websockets to simplify deployment.
|
||||||
* Email address domain whitelisting. (#603)
|
* Email address domain whitelisting. (#603)
|
||||||
* Database exporting to CSV. (#656)
|
* Database exporting to CSV. (#656)
|
||||||
* Imports/Exports rewritten to act as backups.
|
* Imports/Exports rewritten to act as backups.
|
||||||
|
@ -27,6 +46,7 @@ trouble updating your plugins please join [the CTFd Slack](https://slack.ctfd.io
|
||||||
* Based on https://github.com/umpirsky/country-list/blob/master/data/en_US/country.csv.
|
* Based on https://github.com/umpirsky/country-list/blob/master/data/en_US/country.csv.
|
||||||
* Sessions are no longer stored using secure cookies. (#658)
|
* Sessions are no longer stored using secure cookies. (#658)
|
||||||
* Sessions are now stored server side in a cache (`filesystem` or `redis`) allowing for session revocation.
|
* Sessions are now stored server side in a cache (`filesystem` or `redis`) allowing for session revocation.
|
||||||
|
* In order to delete the cache during local development you can delete `CTfd/.data/filesystem_cache`.
|
||||||
* Challenges can now have requirements which must be met before the challenge can be seen/solved.
|
* Challenges can now have requirements which must be met before the challenge can be seen/solved.
|
||||||
* Workshop mode, score hiding, registration hiding, challenge hiding have been changed to visibility settings.
|
* Workshop mode, score hiding, registration hiding, challenge hiding have been changed to visibility settings.
|
||||||
* Users and Teams can now be banned preventing access to the CTF.
|
* Users and Teams can now be banned preventing access to the CTF.
|
||||||
|
@ -34,13 +54,8 @@ trouble updating your plugins please join [the CTFd Slack](https://slack.ctfd.io
|
||||||
* WORKERS count in `docker-entrypoint.sh` defaults to 1. (#716)
|
* WORKERS count in `docker-entrypoint.sh` defaults to 1. (#716)
|
||||||
* `docker-entrypoint.sh` exits on any error. (#717)
|
* `docker-entrypoint.sh` exits on any error. (#717)
|
||||||
* Increased test coverage.
|
* Increased test coverage.
|
||||||
|
* Create `SAFE_MODE` configuration to disable loading of plugins.
|
||||||
* Migrations have been reset.
|
* Migrations have been reset.
|
||||||
* If upgrading from 1.2.0:
|
|
||||||
1. Make all necessary backups. Backup the database, uploads folder, and source code directory.
|
|
||||||
2. Upgrade the source code directory.
|
|
||||||
3. Set the `DATABASE_URL` in `CTFd/config.py`.
|
|
||||||
3. Run the upgrade script from the CTFd folder i.e. `python migrations/1_2_0_upgrade_2_0_0.py`.
|
|
||||||
4. Setup the rest of CTFd and run normally.
|
|
||||||
|
|
||||||
**Themes**
|
**Themes**
|
||||||
|
|
||||||
|
@ -48,24 +63,27 @@ trouble updating your plugins please join [the CTFd Slack](https://slack.ctfd.io
|
||||||
* Javascript uses `fetch()` to consume the REST API.
|
* Javascript uses `fetch()` to consume the REST API.
|
||||||
* The admin theme is no longer considered seperated from the core theme and should always be together.
|
* The admin theme is no longer considered seperated from the core theme and should always be together.
|
||||||
* Themes now use `url_for()` to generate URLs instead of hardcoding.
|
* Themes now use `url_for()` to generate URLs instead of hardcoding.
|
||||||
* socket.io is used to connect to CTFd to receive notifications.
|
* socket.io (via long-polling) is used to connect to CTFd to receive notifications.
|
||||||
* `ctf_name()` renamed to `get_ctf_name()` in themes.
|
* `ctf_name()` renamed to `get_ctf_name()` in themes.
|
||||||
* `ctf_logo()` renamed to `get_ctf_logo()` in themes.
|
* `ctf_logo()` renamed to `get_ctf_logo()` in themes.
|
||||||
* `ctf_theme()` renamed to `get_ctf_theme()` in themes.
|
* `ctf_theme()` renamed to `get_ctf_theme()` in themes.
|
||||||
* Update Font-Awesome to 5.4.1.
|
* Update Font-Awesome to 5.4.1.
|
||||||
* Update moment.js to 2.22.2. (#704)
|
* Update moment.js to 2.22.2. (#704)
|
||||||
|
* Workshop mode, score hiding, registration hiding, challenge hiding have been changed to visibility functions.
|
||||||
|
* `accounts_visible()`, `challenges_visible()`, `registration_visible()`, `scores_visible()`
|
||||||
|
|
||||||
**Plugins**
|
**Plugins**
|
||||||
|
|
||||||
* Plugins are loaded in `sorted()` order
|
* Plugins are loaded in `sorted()` order
|
||||||
* Rename challenge type plugins to use .html and have simplified names. (create, update, view)
|
* Rename challenge type plugins to use `.html` and have simplified names. (create, update, view)
|
||||||
* Many functions moved around because utils.py has been broken up and refactored. (#475)
|
* Many functions have moved around because utils.py has been broken up and refactored. (#475)
|
||||||
* Marshmallow (https://marshmallow.readthedocs.io) is now used by the REST API to validate and serialize/deserialize data.
|
* Marshmallow (https://marshmallow.readthedocs.io) is now used by the REST API to validate and serialize/deserialize API data.
|
||||||
* Marshmallow schemas and views are used to restrict SQLAlchemy columns to user types.
|
* Marshmallow schemas and views are used to restrict SQLAlchemy columns to user roles.
|
||||||
* The REST API features swagger support but this requires more utilization internally.
|
* The REST API features swagger support but this requires more utilization internally.
|
||||||
* Errors can now be provided between routes and decoraters through message flashing. (CTFd.utils.helpers; get_errors, get_infos, info_for, error_for)
|
* Errors can now be provided between routes and decoraters through message flashing. (CTFd.utils.helpers; get_errors, get_infos, info_for, error_for)
|
||||||
* Email registration regex relaxed. (#693)
|
* Email registration regex relaxed. (#693)
|
||||||
* Many functions have moved and now have dedicated utils packages for their category.
|
* Many functions have moved and now have dedicated utils packages for their category.
|
||||||
|
* Create `SAFE_MODE` configuration to disable loading of plugins.
|
||||||
|
|
||||||
|
|
||||||
1.2.0 / 2018-05-04
|
1.2.0 / 2018-05-04
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import division # Use floating point for math calculations
|
||||||
from CTFd.plugins.challenges import BaseChallenge, CHALLENGE_CLASSES
|
from CTFd.plugins.challenges import BaseChallenge, CHALLENGE_CLASSES
|
||||||
from CTFd.plugins import register_plugin_assets_directory
|
from CTFd.plugins import register_plugin_assets_directory
|
||||||
from CTFd.plugins.flags import get_flag_class
|
from CTFd.plugins.flags import get_flag_class
|
||||||
from CTFd.models import db, Solves, Fails, Flags, Challenges, Files, Tags, Teams, Hints
|
from CTFd.models import db, Solves, Fails, Flags, Challenges, ChallengeFiles, Tags, Teams, Hints
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
from CTFd.utils.migrations import upgrade
|
from CTFd.utils.migrations import upgrade
|
||||||
from CTFd.utils.user import get_ip
|
from CTFd.utils.user import get_ip
|
||||||
|
@ -122,10 +122,10 @@ class DynamicValueChallenge(BaseChallenge):
|
||||||
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
files = Files.query.filter_by(challenge_id=challenge.id).all()
|
files = ChallengeFiles.query.filter_by(challenge_id=challenge.id).all()
|
||||||
for f in files:
|
for f in files:
|
||||||
delete_file(f.id)
|
delete_file(f.id)
|
||||||
Files.query.filter_by(challenge_id=challenge.id).delete()
|
ChallengeFiles.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
||||||
DynamicChallenge.query.filter_by(id=challenge.id).delete()
|
DynamicChallenge.query.filter_by(id=challenge.id).delete()
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
Admins Only
|
Admins Only
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
This setting should generally be the same as Account Visibility to avoid conflicts.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -58,6 +61,9 @@
|
||||||
Admins Only
|
Admins Only
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
This setting should generally be the same as Score Visibility to avoid conflicts.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -36,7 +36,7 @@ def update_check(force=False):
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
try:
|
try:
|
||||||
name = get_config('ctf_name') or ''
|
name = str(get_config('ctf_name')) or ''
|
||||||
params = {
|
params = {
|
||||||
'ctf_id': sha256(name),
|
'ctf_id': sha256(name),
|
||||||
'current': app.VERSION,
|
'current': app.VERSION,
|
||||||
|
|
|
@ -15,7 +15,8 @@ CTFd is a Capture The Flag framework focusing on ease of use and customizability
|
||||||
* Unlockable challenge support
|
* Unlockable challenge support
|
||||||
* Challenge plugin architecture to create your own custom challenges
|
* Challenge plugin architecture to create your own custom challenges
|
||||||
* Static & Regex based flags
|
* Static & Regex based flags
|
||||||
* Users can unlock hints for free or with points
|
* Custom flag plugins
|
||||||
|
* Unlockable hints
|
||||||
* File uploads to the server or an Amazon S3-compatible backend
|
* File uploads to the server or an Amazon S3-compatible backend
|
||||||
* Limit challenge attempts & hide challenges
|
* Limit challenge attempts & hide challenges
|
||||||
* Automatic bruteforce protection
|
* Automatic bruteforce protection
|
||||||
|
@ -57,7 +58,7 @@ https://demo.ctfd.io/
|
||||||
## Support
|
## Support
|
||||||
To get basic support, you can join the [CTFd Slack Community](https://slack.ctfd.io/): [![CTFd Slack](https://slack.ctfd.io/badge.svg)](https://slack.ctfd.io/)
|
To get basic support, you can join the [CTFd Slack Community](https://slack.ctfd.io/): [![CTFd Slack](https://slack.ctfd.io/badge.svg)](https://slack.ctfd.io/)
|
||||||
|
|
||||||
If you prefer commercial support or have a special project, send us an email: [support@ctfd.io](mailto:support@ctfd.io).
|
If you prefer commercial support or have a special project, feel free to [contact us](https://ctfd.io/contact/).
|
||||||
|
|
||||||
## Managed Hosting
|
## Managed Hosting
|
||||||
Looking to use CTFd but don't want to deal with managing infrastructure? Check out [the CTFd website](https://ctfd.io/) for managed CTFd deployments.
|
Looking to use CTFd but don't want to deal with managing infrastructure? Check out [the CTFd website](https://ctfd.io/) for managed CTFd deployments.
|
||||||
|
@ -67,7 +68,7 @@ CTFd is heavily integrated with [MajorLeagueCyber](https://majorleaguecyber.org/
|
||||||
|
|
||||||
By registering your CTF event with MajorLeagueCyber users can automatically login, track their individual and team scores, submit writeups, and get notifications of important events.
|
By registering your CTF event with MajorLeagueCyber users can automatically login, track their individual and team scores, submit writeups, and get notifications of important events.
|
||||||
|
|
||||||
To integrate with MajorLeagueCyber, simply register an account, create an event, and install the client ID and client secret in the relevant portion in CTFd/config.py:
|
To integrate with MajorLeagueCyber, simply register an account, create an event, and install the client ID and client secret in the relevant portion in `CTFd/config.py` or in the admin panel:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
OAUTH_CLIENT_ID = None
|
OAUTH_CLIENT_ID = None
|
||||||
|
|
|
@ -168,16 +168,70 @@ if __name__ == '__main__':
|
||||||
print('MIGRATING Config')
|
print('MIGRATING Config')
|
||||||
banned = [
|
banned = [
|
||||||
'ctf_version',
|
'ctf_version',
|
||||||
'setup'
|
|
||||||
]
|
]
|
||||||
|
workshop_mode = None
|
||||||
|
hide_scores = None
|
||||||
|
prevent_registration = None
|
||||||
|
view_challenges_unregistered = None
|
||||||
|
view_scoreboard_if_authed = None
|
||||||
|
|
||||||
|
challenge_visibility = 'private'
|
||||||
|
registration_visibility = 'public'
|
||||||
|
score_visibility = 'public'
|
||||||
|
account_visibility = 'public'
|
||||||
for config in old_data['config']:
|
for config in old_data['config']:
|
||||||
config.pop('id')
|
config.pop('id')
|
||||||
|
|
||||||
|
if config['key'] == 'workshop_mode':
|
||||||
|
workshop_mode = config['value']
|
||||||
|
elif config['key'] == 'hide_scores':
|
||||||
|
hide_scores = config['value']
|
||||||
|
elif config['key'] == 'prevent_registration':
|
||||||
|
prevent_registration = config['value']
|
||||||
|
elif config['key'] == 'view_challenges_unregistered':
|
||||||
|
view_challenges_unregistered = config['value']
|
||||||
|
elif config['key'] == 'view_scoreboard_if_authed':
|
||||||
|
view_scoreboard_if_authed = config['value']
|
||||||
|
|
||||||
if config['key'] not in banned:
|
if config['key'] not in banned:
|
||||||
new_conn['config'].insert(dict(config))
|
new_conn['config'].insert(dict(config))
|
||||||
|
|
||||||
|
if workshop_mode:
|
||||||
|
score_visibility = 'admins'
|
||||||
|
account_visibility = 'admins'
|
||||||
|
|
||||||
|
if hide_scores:
|
||||||
|
score_visibility = 'hidden'
|
||||||
|
|
||||||
|
if prevent_registration:
|
||||||
|
registration_visibility = 'private'
|
||||||
|
|
||||||
|
if view_challenges_unregistered:
|
||||||
|
challenge_visibility = 'public'
|
||||||
|
|
||||||
|
if view_scoreboard_if_authed:
|
||||||
|
score_visibility = 'private'
|
||||||
|
|
||||||
new_conn['config'].insert({
|
new_conn['config'].insert({
|
||||||
'key': 'user_mode',
|
'key': 'user_mode',
|
||||||
'value': 'users'
|
'value': 'users'
|
||||||
})
|
})
|
||||||
|
new_conn['config'].insert({
|
||||||
|
'key': 'challenge_visibility',
|
||||||
|
'value': challenge_visibility
|
||||||
|
})
|
||||||
|
new_conn['config'].insert({
|
||||||
|
'key': 'registration_visibility',
|
||||||
|
'value': registration_visibility
|
||||||
|
})
|
||||||
|
new_conn['config'].insert({
|
||||||
|
'key': 'score_visibility',
|
||||||
|
'value': score_visibility
|
||||||
|
})
|
||||||
|
new_conn['config'].insert({
|
||||||
|
'key': 'account_visibility',
|
||||||
|
'value': account_visibility
|
||||||
|
})
|
||||||
del old_data['config']
|
del old_data['config']
|
||||||
|
|
||||||
manual = []
|
manual = []
|
||||||
|
@ -208,6 +262,7 @@ if __name__ == '__main__':
|
||||||
for table in manual:
|
for table in manual:
|
||||||
print('\t', 'ALTER TABLE `{table}` ADD PRIMARY KEY(id)'.format(table=table))
|
print('\t', 'ALTER TABLE `{table}` ADD PRIMARY KEY(id)'.format(table=table))
|
||||||
|
|
||||||
print('The following tables were not created because they were empty and must be manually recreated (e.g. app.db.create_all()')
|
print(
|
||||||
|
'The following tables were not created because they were empty and must be manually recreated (e.g. app.db.create_all()')
|
||||||
for table in not_created:
|
for table in not_created:
|
||||||
print('\t', table)
|
print('\t', table)
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from CTFd.plugins.dynamic_challenges import DynamicChallenge, DynamicValueChallenge
|
||||||
|
from tests.helpers import *
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_create_dynamic_challenge():
|
||||||
|
"""Test that dynamic challenges can be made from the API/admin panel"""
|
||||||
|
app = create_ctfd(enable_plugins=True)
|
||||||
|
with app.app_context():
|
||||||
|
register_user(app)
|
||||||
|
client = login_as_user(app, name="admin", password="password")
|
||||||
|
|
||||||
|
challenge_data = {
|
||||||
|
"name": "name",
|
||||||
|
"category": "category",
|
||||||
|
"description": "description",
|
||||||
|
"value": 100,
|
||||||
|
"decay": 20,
|
||||||
|
"minimum": 1,
|
||||||
|
"state": "hidden",
|
||||||
|
"type": "dynamic"
|
||||||
|
}
|
||||||
|
|
||||||
|
r = client.post('/api/v1/challenges', json=challenge_data)
|
||||||
|
assert r.get_json().get('data')['id'] == 1
|
||||||
|
|
||||||
|
challenges = DynamicChallenge.query.all()
|
||||||
|
assert len(challenges) == 1
|
||||||
|
|
||||||
|
challenge = challenges[0]
|
||||||
|
assert challenge.value == 100
|
||||||
|
assert challenge.initial == 100
|
||||||
|
assert challenge.decay == 20
|
||||||
|
assert challenge.minimum == 1
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_update_dynamic_challenge():
|
||||||
|
"""Test that dynamic challenges can be deleted"""
|
||||||
|
app = create_ctfd(enable_plugins=True)
|
||||||
|
with app.app_context():
|
||||||
|
challenge_data = {
|
||||||
|
"name": "name",
|
||||||
|
"category": "category",
|
||||||
|
"description": "description",
|
||||||
|
"value": 100,
|
||||||
|
"decay": 20,
|
||||||
|
"minimum": 1,
|
||||||
|
"state": "hidden",
|
||||||
|
"type": "dynamic"
|
||||||
|
}
|
||||||
|
req = FakeRequest(form=challenge_data)
|
||||||
|
challenge = DynamicValueChallenge.create(req)
|
||||||
|
|
||||||
|
assert challenge.value == 100
|
||||||
|
assert challenge.initial == 100
|
||||||
|
assert challenge.decay == 20
|
||||||
|
assert challenge.minimum == 1
|
||||||
|
|
||||||
|
challenge_data = {
|
||||||
|
"name": "new_name",
|
||||||
|
"category": "category",
|
||||||
|
"description": "new_description",
|
||||||
|
"value": "200",
|
||||||
|
"initial": "200",
|
||||||
|
"decay": "40",
|
||||||
|
"minimum": "5",
|
||||||
|
"max_attempts": "0",
|
||||||
|
"state": "visible"
|
||||||
|
}
|
||||||
|
|
||||||
|
req = FakeRequest(form=challenge_data)
|
||||||
|
challenge = DynamicValueChallenge.update(challenge, req)
|
||||||
|
|
||||||
|
assert challenge.name == 'new_name'
|
||||||
|
assert challenge.description == "new_description"
|
||||||
|
assert challenge.value == 200
|
||||||
|
assert challenge.initial == 200
|
||||||
|
assert challenge.decay == 40
|
||||||
|
assert challenge.minimum == 5
|
||||||
|
assert challenge.state == "visible"
|
||||||
|
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_delete_dynamic_challenge():
|
||||||
|
app = create_ctfd(enable_plugins=True)
|
||||||
|
with app.app_context():
|
||||||
|
register_user(app)
|
||||||
|
client = login_as_user(app, name="admin", password="password")
|
||||||
|
|
||||||
|
challenge_data = {
|
||||||
|
"name": "name",
|
||||||
|
"category": "category",
|
||||||
|
"description": "description",
|
||||||
|
"value": 100,
|
||||||
|
"decay": 20,
|
||||||
|
"minimum": 1,
|
||||||
|
"state": "hidden",
|
||||||
|
"type": "dynamic"
|
||||||
|
}
|
||||||
|
|
||||||
|
r = client.post('/api/v1/challenges', json=challenge_data)
|
||||||
|
assert r.get_json().get('data')['id'] == 1
|
||||||
|
|
||||||
|
challenges = DynamicChallenge.query.all()
|
||||||
|
assert len(challenges) == 1
|
||||||
|
|
||||||
|
challenge = challenges[0]
|
||||||
|
DynamicValueChallenge.delete(challenge)
|
||||||
|
|
||||||
|
challenges = DynamicChallenge.query.all()
|
||||||
|
assert len(challenges) == 0
|
||||||
|
destroy_ctfd(app)
|
|
@ -4,6 +4,7 @@ from CTFd.models import *
|
||||||
from CTFd.cache import cache
|
from CTFd.cache import cache
|
||||||
from sqlalchemy_utils import database_exists, create_database, drop_database
|
from sqlalchemy_utils import database_exists, create_database, drop_database
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
|
from collections import namedtuple
|
||||||
import datetime
|
import datetime
|
||||||
import six
|
import six
|
||||||
import gc
|
import gc
|
||||||
|
@ -16,6 +17,9 @@ else:
|
||||||
binary_type = bytes
|
binary_type = bytes
|
||||||
|
|
||||||
|
|
||||||
|
FakeRequest = namedtuple('FakeRequest', ['form'])
|
||||||
|
|
||||||
|
|
||||||
def create_ctfd(ctf_name="CTFd", name="admin", email="admin@ctfd.io", password="password", user_mode="users", setup=True, enable_plugins=False):
|
def create_ctfd(ctf_name="CTFd", name="admin", email="admin@ctfd.io", password="password", user_mode="users", setup=True, enable_plugins=False):
|
||||||
if enable_plugins:
|
if enable_plugins:
|
||||||
TestingConfig.SAFE_MODE = False
|
TestingConfig.SAFE_MODE = False
|
||||||
|
|
Loading…
Reference in New Issue