From 6d9c979b05772e4e9b3cb395e5c591a9c8e710bb Mon Sep 17 00:00:00 2001 From: David Cooke Date: Tue, 6 Apr 2021 23:13:19 +0100 Subject: [PATCH 001/185] prefetch solves --- src/stats/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/views.py b/src/stats/views.py index fa2481fa..e1aad6bc 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -46,7 +46,7 @@ def stats(request): @permission_classes([IsAdminUser]) def full(request): challenge_data = {} - for challenge in Challenge.objects.all(): + for challenge in Challenge.objects.prefetch('solves').all(): challenge_data[challenge.id] = {} challenge_data[challenge.id]["correct"] = challenge.solves.filter(correct=True).count() challenge_data[challenge.id]["incorrect"] = challenge.solves.filter(correct=False).count() From f41d64eb1c6e66e91898afc235fb118fb1f44456 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Tue, 6 Apr 2021 23:24:52 +0100 Subject: [PATCH 002/185] actually prefetch solves --- src/stats/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/views.py b/src/stats/views.py index e1aad6bc..934ae550 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -46,7 +46,7 @@ def stats(request): @permission_classes([IsAdminUser]) def full(request): challenge_data = {} - for challenge in Challenge.objects.prefetch('solves').all(): + for challenge in Challenge.objects.prefetch_related('solves').all(): challenge_data[challenge.id] = {} challenge_data[challenge.id]["correct"] = challenge.solves.filter(correct=True).count() challenge_data[challenge.id]["incorrect"] = challenge.solves.filter(correct=False).count() From 7aa96982c825b3f0cbd688c0e2c866af7b228022 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Tue, 6 Apr 2021 23:41:10 +0100 Subject: [PATCH 003/185] reorder should_deny_admin --- src/member/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/member/models.py b/src/member/models.py index a734ab13..0a4bfec3 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -71,7 +71,7 @@ def has_2fa(self): return hasattr(self, "totp_device") and self.totp_device.verified def should_deny_admin(self): - return not self.has_2fa() and config.get("enable_force_admin_2fa") + return config.get("enable_force_admin_2fa") and not self.has_2fa() class UserIP(ExportModelOperationsMixin("user_ip"), models.Model): From d47825484adedb1361300b5f1e6f30f3721b409c Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 6 Apr 2021 23:42:49 +0100 Subject: [PATCH 004/185] add cachalot to poetry --- poetry.lock | 704 +++++++++++++++++++++++++++---------------------- pyproject.toml | 1 + 2 files changed, 388 insertions(+), 317 deletions(-) diff --git a/poetry.lock b/poetry.lock index a65977e0..69719282 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,71 +1,72 @@ [[package]] -name = "aioredis" -version = "1.3.1" -description = "asyncio (PEP 3156) Redis support" category = "main" +description = "asyncio (PEP 3156) Redis support" +name = "aioredis" optional = false python-versions = "*" +version = "1.3.1" [package.dependencies] async-timeout = "*" hiredis = "*" [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" optional = false python-versions = "*" +version = "1.4.4" [[package]] -name = "appnope" -version = "0.1.2" -description = "Disable App Nap on macOS >= 10.9" category = "dev" +description = "Disable App Nap on macOS >= 10.9" +marker = "sys_platform == \"darwin\"" +name = "appnope" optional = false python-versions = "*" +version = "0.1.2" [[package]] -name = "asgiref" -version = "3.3.1" -description = "ASGI specs, helper code, and adapters" category = "main" +description = "ASGI specs, helper code, and adapters" +name = "asgiref" optional = false python-versions = ">=3.5" +version = "3.3.1" [package.extras] tests = ["pytest", "pytest-asyncio"] [[package]] -name = "async-timeout" -version = "3.0.1" -description = "Timeout context manager for asyncio programs" category = "main" +description = "Timeout context manager for asyncio programs" +name = "async-timeout" optional = false python-versions = ">=3.5.3" +version = "3.0.1" [[package]] -name = "attrs" -version = "20.3.0" -description = "Classes Without Boilerplate" category = "main" +description = "Classes Without Boilerplate" +name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3.0" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -name = "autobahn" -version = "20.12.3" -description = "WebSocket client & server library, WAMP real-time framework" category = "main" +description = "WebSocket client & server library, WAMP real-time framework" +name = "autobahn" optional = false python-versions = ">=3.6" +version = "20.12.3" [package.dependencies] cryptography = ">=2.9.2" @@ -85,12 +86,12 @@ twisted = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)"] xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] [[package]] -name = "automat" -version = "20.2.0" -description = "Self-service finite-state machines for the programmer on the go." category = "main" +description = "Self-service finite-state machines for the programmer on the go." +name = "automat" optional = false python-versions = "*" +version = "20.2.0" [package.dependencies] attrs = ">=19.2.0" @@ -100,32 +101,32 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -name = "autopep8" -version = "1.5.4" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "main" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +name = "autopep8" optional = false python-versions = "*" +version = "1.5.4" [package.dependencies] pycodestyle = ">=2.6.0" toml = "*" [[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" category = "dev" +description = "Specifications for callback functions passed in to an API" +name = "backcall" optional = false python-versions = "*" +version = "0.2.0" [[package]] -name = "black" -version = "20.8b1" -description = "The uncompromising code formatter." category = "dev" +description = "The uncompromising code formatter." +name = "black" optional = false python-versions = ">=3.6" +version = "20.8b1" [package.dependencies] appdirs = "*" @@ -142,12 +143,12 @@ colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -name = "boto3" -version = "1.16.43" -description = "The AWS SDK for Python" category = "main" +description = "The AWS SDK for Python" +name = "boto3" optional = false python-versions = "*" +version = "1.16.43" [package.dependencies] botocore = ">=1.19.43,<1.20.0" @@ -155,60 +156,63 @@ jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" [[package]] -name = "botocore" -version = "1.19.43" -description = "Low-level, data-driven core of boto 3." category = "main" +description = "Low-level, data-driven core of boto 3." +name = "botocore" optional = false python-versions = "*" +version = "1.19.43" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<1.27", markers = "python_version != \"3.4\""} + +[package.dependencies.urllib3] +python = "<3.4.0 || >=3.5.0" +version = ">=1.25.4,<1.27" [[package]] -name = "certifi" -version = "2020.12.5" -description = "Python package for providing Mozilla's CA Bundle." category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" optional = false python-versions = "*" +version = "2020.12.5" [[package]] -name = "cffi" -version = "1.14.4" -description = "Foreign Function Interface for Python calling C code." category = "main" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" optional = false python-versions = "*" +version = "1.14.4" [package.dependencies] pycparser = "*" [[package]] -name = "channels" -version = "2.4.0" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." category = "main" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +name = "channels" optional = false python-versions = ">=3.5" +version = "2.4.0" [package.dependencies] +Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" -Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -name = "channels-redis" -version = "2.4.1" -description = "Redis-backed ASGI channel layer implementation" category = "main" +description = "Redis-backed ASGI channel layer implementation" +name = "channels-redis" optional = false python-versions = ">=3.6" +version = "2.4.1" [package.dependencies] aioredis = ">=1.0,<2.0" @@ -221,44 +225,45 @@ cryptography = ["cryptography (>=1.3.0)"] tests = ["cryptography (>=1.3.0)", "pytest (>=3.6.0,<3.7.0)", "pytest-asyncio (>=0.8,<1.0)", "async-generator (>=1.8,<2.0)", "async-timeout (>=2.0,<3.0)"] [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.0.0" [[package]] -name = "click" -version = "7.1.2" -description = "Composable command line interface toolkit" category = "dev" +description = "Composable command line interface toolkit" +name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" [[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.4" [[package]] -name = "constantly" -version = "15.1.0" -description = "Symbolic constants in Python" category = "main" +description = "Symbolic constants in Python" +name = "constantly" optional = false python-versions = "*" +version = "15.1.0" [[package]] -name = "coreapi" -version = "2.3.3" -description = "Python client library for Core API." category = "dev" +description = "Python client library for Core API." +name = "coreapi" optional = false python-versions = "*" +version = "2.3.3" [package.dependencies] coreschema = "*" @@ -267,77 +272,80 @@ requests = "*" uritemplate = "*" [[package]] -name = "coreschema" -version = "0.0.4" -description = "Core Schema." category = "dev" +description = "Core Schema." +name = "coreschema" optional = false python-versions = "*" +version = "0.0.4" [package.dependencies] jinja2 = "*" [[package]] -name = "coverage" -version = "5.3.1" -description = "Code coverage measurement for Python" category = "dev" +description = "Code coverage measurement for Python" +name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.3.1" [package.extras] toml = ["toml"] [[package]] -name = "cryptography" -version = "3.3.1" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +name = "cryptography" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +version = "3.3.1" [package.dependencies] cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] [[package]] -name = "daphne" -version = "2.5.0" -description = "Django ASGI (HTTP/WebSocket) server" category = "main" +description = "Django ASGI (HTTP/WebSocket) server" +name = "daphne" optional = false python-versions = "*" +version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" -twisted = {version = ">=18.7", extras = ["tls"]} + +[package.dependencies.twisted] +extras = ["tls"] +version = ">=18.7" [package.extras] -tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] [[package]] -name = "decorator" -version = "4.4.2" -description = "Decorators for Humans" category = "dev" +description = "Decorators for Humans" +name = "decorator" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" [[package]] -name = "django" -version = "3.1.4" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" optional = false python-versions = ">=3.6" +version = "3.1.4" [package.dependencies] asgiref = ">=3.2.10,<4" @@ -349,88 +357,99 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -name = "django-cors-headers" -version = "3.2.1" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." category = "main" +description = "Caches your Django ORM queries and automatically invalidates them." +name = "django-cachalot" +optional = false +python-versions = "*" +version = "2.3.5" + +[package.dependencies] +Django = ">=2" + +[[package]] +category = "main" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +name = "django-cors-headers" optional = false python-versions = ">=3.5" +version = "3.2.1" [package.dependencies] Django = ">=1.11" [[package]] -name = "django-filter" -version = "2.4.0" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." category = "main" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +name = "django-filter" optional = false python-versions = ">=3.5" +version = "2.4.0" [package.dependencies] Django = ">=2.2" [[package]] -name = "django-prometheus" -version = "2.1.0" -description = "Django middlewares to monitor your application with Prometheus.io." category = "main" +description = "Django middlewares to monitor your application with Prometheus.io." +name = "django-prometheus" optional = false python-versions = "*" +version = "2.1.0" [package.dependencies] prometheus-client = ">=0.7" [[package]] -name = "django-redis" -version = "4.11.0" -description = "Full featured redis cache backend for Django." category = "main" +description = "Full featured redis cache backend for Django." +name = "django-redis" optional = false python-versions = ">=3.5" +version = "4.11.0" [package.dependencies] Django = ">=1.11" redis = ">=2.10.0" [[package]] -name = "django-redis-cache" -version = "2.1.1" -description = "Redis Cache Backend for Django" category = "main" +description = "Redis Cache Backend for Django" +name = "django-redis-cache" optional = false python-versions = "*" +version = "2.1.1" [package.dependencies] redis = "<4.0" six = "*" [[package]] -name = "django-silk" -version = "4.0.1" -description = "Silky smooth profiling for the Django Framework" category = "main" +description = "Silky smooth profiling for the Django Framework" +name = "django-silk" optional = false python-versions = ">=3.5" +version = "4.0.1" [package.dependencies] -autopep8 = "*" Django = ">=2.2" -gprof2dot = ">=2017.09.19" Jinja2 = "*" Pygments = "*" +autopep8 = "*" +gprof2dot = ">=2017.09.19" python-dateutil = "*" pytz = "*" requests = "*" sqlparse = "*" [[package]] -name = "django-storages" -version = "1.9.1" -description = "Support for many storage backends in Django" category = "main" +description = "Support for many storage backends in Django" +name = "django-storages" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.9.1" [package.dependencies] Django = ">=1.11" @@ -444,12 +463,12 @@ libcloud = ["apache-libcloud"] sftp = ["paramiko"] [[package]] -name = "django-stubs" -version = "1.7.0" -description = "Mypy stubs for Django" category = "dev" +description = "Mypy stubs for Django" +name = "django-stubs" optional = false python-versions = ">=3.6" +version = "1.7.0" [package.dependencies] django = "*" @@ -457,35 +476,35 @@ mypy = ">=0.790" typing-extensions = "*" [[package]] -name = "django-zxcvbn-password-validator" -version = "1.3.0" -description = "A translatable password validator for django, based on zxcvbn-python." category = "main" +description = "A translatable password validator for django, based on zxcvbn-python." +name = "django-zxcvbn-password-validator" optional = false python-versions = "*" +version = "1.3.0" [package.dependencies] Django = ">=2.0" zxcvbn = "*" [[package]] -name = "djangorestframework" -version = "3.11.1" -description = "Web APIs for Django, made easy." category = "main" +description = "Web APIs for Django, made easy." +name = "djangorestframework" optional = false python-versions = ">=3.5" +version = "3.11.1" [package.dependencies] django = ">=1.11" [[package]] -name = "djangorestframework-stubs" -version = "1.3.0" -description = "PEP-484 stubs for django-rest-framework" category = "dev" +description = "PEP-484 stubs for django-rest-framework" +name = "djangorestframework-stubs" optional = false python-versions = ">=3.6" +version = "1.3.0" [package.dependencies] coreapi = ">=2.0.0" @@ -495,20 +514,23 @@ requests = ">=2.0.0" typing-extensions = ">=3.7.2" [[package]] -name = "gprof2dot" -version = "2019.11.30" -description = "Generate a dot graph from the output of several profilers." category = "main" +description = "Generate a dot graph from the output of several profilers." +name = "gprof2dot" optional = false python-versions = "*" +version = "2019.11.30" [[package]] -name = "gunicorn" -version = "20.0.4" -description = "WSGI HTTP Server for UNIX" category = "main" +description = "WSGI HTTP Server for UNIX" +name = "gunicorn" optional = false python-versions = ">=3.4" +version = "20.0.4" + +[package.dependencies] +setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.9.7)"] @@ -517,61 +539,62 @@ setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] [[package]] -name = "hiredis" -version = "1.1.0" -description = "Python wrapper for hiredis" category = "main" +description = "Python wrapper for hiredis" +name = "hiredis" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" [[package]] -name = "hyperlink" -version = "20.0.1" -description = "A featureful, immutable, and correct URL for Python." category = "main" +description = "A featureful, immutable, and correct URL for Python." +name = "hyperlink" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.0.1" [package.dependencies] idna = ">=2.5" [[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.10" [[package]] -name = "incremental" -version = "17.5.0" -description = "" category = "main" +description = "" +name = "incremental" optional = false python-versions = "*" +version = "17.5.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] -name = "ipython" -version = "7.19.0" -description = "IPython: Productive Interactive Computing" category = "dev" +description = "IPython: Productive Interactive Computing" +name = "ipython" optional = false python-versions = ">=3.7" +version = "7.19.0" [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} +appnope = "*" backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} +colorama = "*" decorator = "*" jedi = ">=0.10" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pexpect = ">4.3" pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" +setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -586,43 +609,43 @@ qtconsole = ["qtconsole"] test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] [[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" category = "dev" +description = "Vestigial utilities from IPython" +name = "ipython-genutils" optional = false python-versions = "*" +version = "0.2.0" [[package]] -name = "itypes" -version = "1.2.0" -description = "Simple immutable types for python." category = "dev" +description = "Simple immutable types for python." +name = "itypes" optional = false python-versions = "*" +version = "1.2.0" [[package]] -name = "jedi" -version = "0.18.0" -description = "An autocompletion tool for Python that can be used for text editors." category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +name = "jedi" optional = false python-versions = ">=3.6" +version = "0.18.0" [package.dependencies] parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] -name = "jinja2" -version = "2.11.2" -description = "A very fast and expressive template engine." category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -631,36 +654,36 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -name = "jmespath" -version = "0.10.0" -description = "JSON Matching Expressions" category = "main" +description = "JSON Matching Expressions" +name = "jmespath" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.10.0" [[package]] -name = "markupsafe" -version = "1.1.1" -description = "Safely add untrusted strings to HTML/XML markup." category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" [[package]] -name = "msgpack" -version = "0.6.2" -description = "MessagePack (de)serializer." category = "main" +description = "MessagePack (de)serializer." +name = "msgpack" optional = false python-versions = "*" +version = "0.6.2" [[package]] -name = "mypy" -version = "0.790" -description = "Optional static typing for Python" category = "dev" +description = "Optional static typing for Python" +name = "mypy" optional = false python-versions = ">=3.5" +version = "0.790" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -671,151 +694,153 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" optional = false python-versions = "*" +version = "0.4.3" [[package]] -name = "newrelic" -version = "5.24.0.153" -description = "New Relic Python Agent" category = "main" +description = "New Relic Python Agent" +name = "newrelic" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "5.24.0.153" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] [[package]] -name = "parso" -version = "0.8.1" -description = "A Python Parser" category = "dev" +description = "A Python Parser" +name = "parso" optional = false python-versions = ">=3.6" +version = "0.8.1" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] -name = "pathspec" -version = "0.8.1" -description = "Utility library for gitignore style pattern matching of file paths." category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.8.1" [[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "sys_platform != \"win32\"" +name = "pexpect" optional = false python-versions = "*" +version = "4.8.0" [package.dependencies] ptyprocess = ">=0.5" [[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +name = "pickleshare" optional = false python-versions = "*" +version = "0.7.5" [[package]] -name = "prometheus-client" -version = "0.9.0" -description = "Python client for the Prometheus monitoring system." category = "main" +description = "Python client for the Prometheus monitoring system." +name = "prometheus-client" optional = false python-versions = "*" +version = "0.9.0" [package.extras] twisted = ["twisted"] [[package]] -name = "prompt-toolkit" -version = "3.0.8" -description = "Library for building powerful interactive command lines in Python" category = "dev" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" optional = false python-versions = ">=3.6.1" +version = "3.0.8" [package.dependencies] wcwidth = "*" [[package]] -name = "psycopg2-binary" -version = "2.8.6" -description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg2-binary" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "2.8.6" [[package]] -name = "ptyprocess" -version = "0.6.0" -description = "Run a subprocess in a pseudo terminal" category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "sys_platform != \"win32\"" +name = "ptyprocess" optional = false python-versions = "*" +version = "0.6.0" [[package]] -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" category = "main" +description = "ASN.1 types and codecs" +name = "pyasn1" optional = false python-versions = "*" +version = "0.4.8" [[package]] -name = "pyasn1-modules" -version = "0.2.8" -description = "A collection of ASN.1-based protocols modules." category = "main" +description = "A collection of ASN.1-based protocols modules." +name = "pyasn1-modules" optional = false python-versions = "*" +version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -name = "pycodestyle" -version = "2.6.0" -description = "Python style guide checker" category = "main" +description = "Python style guide checker" +name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" [[package]] -name = "pycparser" -version = "2.20" -description = "C parser in Python" category = "main" +description = "C parser in Python" +name = "pycparser" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" [[package]] -name = "pygments" -version = "2.7.3" -description = "Pygments is a syntax highlighting package written in Python." category = "main" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" optional = false python-versions = ">=3.5" +version = "2.7.3" [[package]] -name = "pyopenssl" -version = "20.0.1" -description = "Python wrapper module around the OpenSSL library" category = "main" +description = "Python wrapper module around the OpenSSL library" +name = "pyopenssl" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "20.0.1" [package.dependencies] cryptography = ">=3.2" @@ -826,74 +851,74 @@ docs = ["sphinx", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] -name = "pyotp" -version = "2.3.0" -description = "Python One Time Password Library" category = "main" +description = "Python One Time Password Library" +name = "pyotp" optional = false python-versions = "*" +version = "2.3.0" [[package]] -name = "python-dateutil" -version = "2.8.1" -description = "Extensions to the standard Python datetime module" category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -name = "python-http-client" -version = "3.3.1" -description = "HTTP REST client, simplified for Python" category = "main" +description = "HTTP REST client, simplified for Python" +name = "python-http-client" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.3.1" [[package]] -name = "pytz" -version = "2020.5" -description = "World timezone definitions, modern and historical" category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" optional = false python-versions = "*" +version = "2020.5" [[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" category = "dev" +description = "YAML parser and emitter for Python" +name = "pyyaml" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +version = "5.4.1" [[package]] -name = "redis" -version = "3.5.3" -description = "Python client for Redis key-value store" category = "main" +description = "Python client for Redis key-value store" +name = "redis" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -name = "regex" -version = "2020.11.13" -description = "Alternative regular expression module, to replace re." category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" optional = false python-versions = "*" +version = "2020.11.13" [[package]] -name = "requests" -version = "2.25.1" -description = "Python HTTP for Humans." category = "main" +description = "Python HTTP for Humans." +name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.25.1" [package.dependencies] certifi = ">=2017.4.17" @@ -903,38 +928,38 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] -name = "s3transfer" -version = "0.3.3" -description = "An Amazon S3 Transfer Manager" category = "main" +description = "An Amazon S3 Transfer Manager" +name = "s3transfer" optional = false python-versions = "*" +version = "0.3.3" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [[package]] -name = "sendgrid" -version = "6.4.8" -description = "Twilio SendGrid library for Python" category = "main" +description = "Twilio SendGrid library for Python" +name = "sendgrid" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "6.4.8" [package.dependencies] python-http-client = ">=3.2.1" starkbank-ecdsa = ">=1.0.0" [[package]] -name = "sentry-sdk" -version = "0.16.5" -description = "Python client for Sentry (https://sentry.io)" category = "main" +description = "Python client for Sentry (https://sentry.io)" +name = "sentry-sdk" optional = false python-versions = "*" +version = "0.16.5" [package.dependencies] certifi = "*" @@ -956,12 +981,12 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] [[package]] -name = "service-identity" -version = "18.1.0" -description = "Service identity verification for pyOpenSSL & cryptography." category = "main" +description = "Service identity verification for pyOpenSSL & cryptography." +name = "service-identity" optional = false python-versions = "*" +version = "18.1.0" [package.dependencies] attrs = ">=16.0.0" @@ -976,44 +1001,44 @@ idna = ["idna"] tests = ["coverage (>=4.2.0)", "pytest"] [[package]] -name = "six" -version = "1.15.0" -description = "Python 2 and 3 compatibility utilities" category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" [[package]] -name = "sqlparse" -version = "0.4.1" -description = "A non-validating SQL parser." category = "main" +description = "A non-validating SQL parser." +name = "sqlparse" optional = false python-versions = ">=3.5" +version = "0.4.1" [[package]] -name = "starkbank-ecdsa" -version = "1.1.0" -description = "A lightweight and fast pure python ECDSA library" category = "main" +description = "A lightweight and fast pure python ECDSA library" +name = "starkbank-ecdsa" optional = false python-versions = "*" +version = "1.1.0" [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" category = "main" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.10.2" [[package]] -name = "traitlets" -version = "5.0.5" -description = "Traitlets Python configuration system" category = "dev" +description = "Traitlets Python configuration system" +name = "traitlets" optional = false python-versions = ">=3.7" +version = "5.0.5" [package.dependencies] ipython-genutils = "*" @@ -1022,25 +1047,34 @@ ipython-genutils = "*" test = ["pytest"] [[package]] -name = "twisted" -version = "21.2.0" -description = "An asynchronous networking framework written in Python" category = "main" +description = "An asynchronous networking framework written in Python" +name = "twisted" optional = false python-versions = ">=3.5.4" +version = "21.2.0" [package.dependencies] -attrs = ">=19.2.0" Automat = ">=0.8.0" +attrs = ">=19.2.0" constantly = ">=15.1" hyperlink = ">=17.1.1" -idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" -pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} -service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} -twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} +twisted-iocpsupport = ">=1.0.0,<1.1.0" "zope.interface" = ">=4.4.2" +[package.dependencies.idna] +optional = true +version = ">=2.4" + +[package.dependencies.pyopenssl] +optional = true +version = ">=16.0.0" + +[package.dependencies.service-identity] +optional = true +version = ">=18.1.0" + [package.extras] all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] @@ -1056,78 +1090,82 @@ tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] [[package]] -name = "twisted-iocpsupport" -version = "1.0.1" -description = "An extension for use in the twisted I/O Completion Ports reactor." category = "main" +description = "An extension for use in the twisted I/O Completion Ports reactor." +marker = "platform_system == \"Windows\"" +name = "twisted-iocpsupport" optional = false python-versions = "*" +version = "1.0.1" [[package]] -name = "txaio" -version = "20.12.1" -description = "Compatibility API between asyncio/Twisted/Trollius" category = "main" +description = "Compatibility API between asyncio/Twisted/Trollius" +name = "txaio" optional = false python-versions = ">=3.6" +version = "20.12.1" [package.extras] all = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] twisted = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] [[package]] -name = "typed-ast" -version = "1.4.1" -description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" optional = false python-versions = "*" +version = "1.4.1" [[package]] -name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" optional = false python-versions = "*" +version = "3.7.4.3" [[package]] -name = "uritemplate" -version = "3.0.1" -description = "URI templates" category = "dev" +description = "URI templates" +name = "uritemplate" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.0.1" [[package]] -name = "urllib3" -version = "1.26.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.26.2" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" optional = false python-versions = "*" +version = "0.2.5" [[package]] -name = "zope.interface" -version = "5.2.0" -description = "Interfaces for Python" category = "main" +description = "Interfaces for Python" +name = "zope.interface" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.2.0" + +[package.dependencies] +setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1135,17 +1173,17 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [[package]] -name = "zxcvbn" -version = "4.4.28" -description = "" category = "main" +description = "" +name = "zxcvbn" optional = false python-versions = "*" +version = "4.4.28" [metadata] -lock-version = "1.1" +content-hash = "6ad40bd4a1528ccc0d539f97248fe590d9c258abcd91da2e4d33187ccedc1338" +lock-version = "1.0" python-versions = "^3.8" -content-hash = "53dc2a3c8189802d7712c53e3812d651a8a083d8e7e1fe74ee1e79a53b7d0da1" [metadata.files] aioredis = [ @@ -1236,6 +1274,7 @@ cffi = [ {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"}, {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, @@ -1351,6 +1390,10 @@ django = [ {file = "Django-3.1.4-py3-none-any.whl", hash = "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2"}, {file = "Django-3.1.4.tar.gz", hash = "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"}, ] +django-cachalot = [ + {file = "django-cachalot-2.3.5.tar.gz", hash = "sha256:02afabb6e83f5f06c87a7e6f01ebcdbc52a4156ec849da8e68b14498bc474d3e"}, + {file = "django_cachalot-2.3.5-py3-none-any.whl", hash = "sha256:ed0782f9702ead95337692f0fae8bbb9352a106490f272d9b76e86b1da81c7e3"}, +] django-cors-headers = [ {file = "django-cors-headers-3.2.1.tar.gz", hash = "sha256:a5960addecc04527ab26617e51b8ed42f0adab4594b24bb0f3c33e2bd3857c3f"}, {file = "django_cors_headers-3.2.1-py3-none-any.whl", hash = "sha256:a785b5f446f6635810776d9f5f5d23e6a2a2f728ea982648370afaf0dfdf2627"}, @@ -1503,20 +1546,39 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] msgpack = [ @@ -1710,18 +1772,26 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, diff --git a/pyproject.toml b/pyproject.toml index caa7a978..286fee99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ django-filter = "^2.3.0" sendgrid = "^6.4.7" newrelic = "^5.22.1" django-prometheus = "^2.1.0" +django-cachalot = "^2.3.5" [tool.poetry.dev-dependencies] ipython = "^7.19.0" From 6f14d2f4fd83bab871ed2ba26c6ec67ea60669d6 Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 6 Apr 2021 23:44:55 +0100 Subject: [PATCH 005/185] add cachalot to INSTALLED_APPS --- src/backend/settings/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index 10d4d86f..f9cc17ff 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -72,6 +72,7 @@ "channels", "storages", "corsheaders", + "cachahalot", "django_prometheus", "django.contrib.auth", "django.contrib.contenttypes", From 7196ae2471e62f4e7e2ee6002f9d6f95aad868d4 Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 6 Apr 2021 23:47:29 +0100 Subject: [PATCH 006/185] spelling is hard --- src/backend/settings/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index f9cc17ff..2575c0e8 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -72,7 +72,7 @@ "channels", "storages", "corsheaders", - "cachahalot", + "cachalot", "django_prometheus", "django.contrib.auth", "django.contrib.contenttypes", From 47b70a76132af8b9ecf1fa1262c9840399694958 Mon Sep 17 00:00:00 2001 From: BenTechy66 Date: Wed, 7 Apr 2021 14:37:39 +0100 Subject: [PATCH 007/185] 2 hour wait -> 15 minute wait --- .../commands/insert_mega_dummy_data.py | 128 +++++++++++++----- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 2ebf3463..13fb30a8 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -1,12 +1,21 @@ import random +import time from django.contrib.auth import get_user_model from django.core.management import BaseCommand +from django.db import transaction from challenge.models import Challenge, Category, Score, Solve from team.models import Team +def timed_log(call, msg): + before_call = time.time() + print(msg, end=" ", flush=True) + call() + print("Done (" + str(time.time() - before_call) + "s)") + + class Command(BaseCommand): def handle(self, *args, **options): @@ -17,60 +26,105 @@ def handle(self, *args, **options): category.save() print('Creating 100 challenges for each category...') + + def random_rpn_op(depth=0): + depth += 1 + + if depth > 4 or (random.randint(1, 4) < 3 and depth > 1): + return str(random.randint(1, 1000)) + + if random.randint(1, 2) == 1: + return f"{random_rpn_op(depth)} {random_rpn_op(depth)} OR" + else: + return f"{random_rpn_op(depth)} {random_rpn_op(depth)} AND" + for i in range(10): category = Category.objects.get(name='category-' + str(i)) - for j in range(50): - challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, - description='An example challenge ' + str(j), - flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', auto_unlock=True, score=j, - challenge_metadata={}) - challenge.save() - for j in range(50, 100, 2): + for j in range(100): + auto_unlock = (random.randint(1, 2) == 1) challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, description='An example challenge ' + str(j), - flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', auto_unlock=True, - score=j, challenge_metadata={}) - challenge2 = Challenge(name='cat-' + str(i) + '-chal-' + str(j + 1), category=category, - description='An example challenge ' + str(j + 1), - flag_metadata={'flag': f'ractf{{{j + 1}}}'}, author='dave', auto_unlock=False, - score=j, challenge_metadata={}) - challenge2.save() + flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', auto_unlock=auto_unlock, + score=j, challenge_metadata={}, + unlock_requirements=random_rpn_op() if not auto_unlock else "") challenge.save() - challenge.unlocks.add(challenge2) - print('Creating 20000 users with 10000 teams with 100 solves per team...') + print('Creating 20000 users... ', end="", flush=True) + Member = get_user_model() + users_to_create = [] + for i in range(10000): + user = Member(username='user-' + str(i), email='user-' + str(i) + '@example.org') + user2 = Member(username='user-' + str(i) + '-second', + email='user-' + str(i) + '-second@example.org') + users_to_create.append(user) + users_to_create.append(user2) + + timed_log(lambda: Member.objects.bulk_create(users_to_create), "Inserting to database...") + + print('Creating 10000 teams... ', end="", flush=True) + teams_to_create = [] + members = list(Member.objects.all()) for i in range(10000): - user = get_user_model()(username='user-' + str(i), email='user-' + str(i) + '@example.org') - user.save() - team = Team(name='team-' + str(i), password='password', owner=user) - team.save() - user2 = get_user_model()(username='user-' + str(i) + '-second', email='user-' + str(i) + '-second@example.org', - team=team) - user2.save() + team = Team(name='team-' + str(i), password='password', owner=members[i * 2]) + + teams_to_create.append(team) + + timed_log(lambda: Team.objects.bulk_create(teams_to_create), "Inserting to database...") + + print("Adding members to teams... ", end="", flush=True) + members_to_update = [] + teams = list(Team.objects.all()) + for i in range(0, len(members), 2): + owner = members[i] + teammate = members[i+1] + + owner.team = teams[i // 2] + teammate.team = teams[i // 2] + + members_to_update.append(owner) + members_to_update.append(teammate) + + timed_log(lambda: Member.objects.bulk_update(members_to_update, ["team"]), "Saving to database...") + + print("Creating 1m solves and scores...") + scores_to_create = [] + solves_to_create = [] + users_to_update = [] + teams_to_update = [] + for team in Team.objects.prefetch_related("members").all(): + z = team.members.all() + user = z[0] + user2 = z[1] + used = [] for j in range(50): - challenge = Category.objects.get(name='category-' + str(j % 5))\ - .category_challenges.get(name='cat-' + str(j % 5) + '-chal-' + str(j)) - points = random.randint(0, 1000) + points = random.randint(0, 999) score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) - score.save() - solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, + scores_to_create.append(score) + solve = Solve(team=team, solved_by=user, challenge_id=(j * 19) + (team.id % 20) + 1, first_blood=False, flag='ractf{}', score=score, correct=True) - solve.save() + solves_to_create.append(solve) user.points += points team.points += points user.leaderboard_points += points team.leaderboard_points += points - for j in range(50): - challenge = Category.objects.get(name='category-' + str(j % 5 + 5))\ - .category_challenges.get(name='cat-' + str(j % 5 + 5) + '-chal-' + str(j)) - points = random.randint(0, 1000) + + points = random.randint(0, 999) score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) - score.save() - solve = Solve(team=team, solved_by=user2, challenge=challenge, - first_blood=challenge.first_blood is None, flag='ractf{}', score=score, correct=True) - solve.save() + scores_to_create.append(score) + solve = Solve(team=team, solved_by=user2, challenge_id=(j * 19) + (team.id % 20) + 2, + first_blood=False, flag='ractf{}', score=score, correct=True) + solves_to_create.append(solve) user2.points += points team.points += points user.leaderboard_points += points team.leaderboard_points += points + teams_to_update.append(team) + users_to_update.append(user) + users_to_update.append(user2) + + timed_log(lambda: Solve.objects.bulk_create(solves_to_create), "[1/4] Saving Solves in database...") + timed_log(lambda: Member.objects.bulk_update(members_to_update, ["leaderboard_points"]), "[2/4] Saving Members in database...") + timed_log(lambda: Team.objects.bulk_update(teams_to_update, ["leaderboard_points"]), "[3/4] Saving Teams in database...") + timed_log(lambda: Score.objects.bulk_create(scores_to_create), "[4/4] Saving Scores in database...") + From c8cd21a2c6627845a467d3febfcd3c11e498bc80 Mon Sep 17 00:00:00 2001 From: bentechy Date: Wed, 7 Apr 2021 15:08:24 +0100 Subject: [PATCH 008/185] log how long stuff took --- .../commands/insert_mega_dummy_data.py | 240 +++++++++--------- 1 file changed, 127 insertions(+), 113 deletions(-) diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 13fb30a8..18831140 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -9,122 +9,136 @@ from team.models import Team -def timed_log(call, msg): - before_call = time.time() - print(msg, end=" ", flush=True) - call() - print("Done (" + str(time.time() - before_call) + "s)") +class TimedLog: + def __init__(self, msg, ending=" "): + self.msg = msg + self.ending = ending + + def __enter__(self): + self._entry_time = time.time() + print(self.msg, end=self.ending, flush=True) + + def __exit__(self, exc_type, exc_val, exc_tb): + print("Done (" + str(time.time() - self._entry_time) + "s)") class Command(BaseCommand): def handle(self, *args, **options): - print('Creating 10 categories...') - for i in range(10): - category = Category(name='category-' + str(i), display_order=i, contained_type='test', - description='Category ' + str(i)) - category.save() - - print('Creating 100 challenges for each category...') - - def random_rpn_op(depth=0): - depth += 1 - - if depth > 4 or (random.randint(1, 4) < 3 and depth > 1): - return str(random.randint(1, 1000)) - - if random.randint(1, 2) == 1: - return f"{random_rpn_op(depth)} {random_rpn_op(depth)} OR" - else: - return f"{random_rpn_op(depth)} {random_rpn_op(depth)} AND" - - for i in range(10): - category = Category.objects.get(name='category-' + str(i)) - for j in range(100): - auto_unlock = (random.randint(1, 2) == 1) - challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, - description='An example challenge ' + str(j), - flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', auto_unlock=auto_unlock, - score=j, challenge_metadata={}, - unlock_requirements=random_rpn_op() if not auto_unlock else "") - challenge.save() - - print('Creating 20000 users... ', end="", flush=True) - Member = get_user_model() - users_to_create = [] - for i in range(10000): - user = Member(username='user-' + str(i), email='user-' + str(i) + '@example.org') - user2 = Member(username='user-' + str(i) + '-second', - email='user-' + str(i) + '-second@example.org') - users_to_create.append(user) - users_to_create.append(user2) - - timed_log(lambda: Member.objects.bulk_create(users_to_create), "Inserting to database...") - - print('Creating 10000 teams... ', end="", flush=True) - teams_to_create = [] - members = list(Member.objects.all()) - for i in range(10000): - team = Team(name='team-' + str(i), password='password', owner=members[i * 2]) - - teams_to_create.append(team) - - timed_log(lambda: Team.objects.bulk_create(teams_to_create), "Inserting to database...") - - print("Adding members to teams... ", end="", flush=True) - members_to_update = [] - teams = list(Team.objects.all()) - for i in range(0, len(members), 2): - owner = members[i] - teammate = members[i+1] - - owner.team = teams[i // 2] - teammate.team = teams[i // 2] - - members_to_update.append(owner) - members_to_update.append(teammate) - - timed_log(lambda: Member.objects.bulk_update(members_to_update, ["team"]), "Saving to database...") - - print("Creating 1m solves and scores...") - scores_to_create = [] - solves_to_create = [] - users_to_update = [] - teams_to_update = [] - for team in Team.objects.prefetch_related("members").all(): - z = team.members.all() - user = z[0] - user2 = z[1] - used = [] - for j in range(50): - points = random.randint(0, 999) - score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) - scores_to_create.append(score) - solve = Solve(team=team, solved_by=user, challenge_id=(j * 19) + (team.id % 20) + 1, first_blood=False, - flag='ractf{}', score=score, correct=True) - solves_to_create.append(solve) - user.points += points - team.points += points - user.leaderboard_points += points - team.leaderboard_points += points - - points = random.randint(0, 999) - score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) - scores_to_create.append(score) - solve = Solve(team=team, solved_by=user2, challenge_id=(j * 19) + (team.id % 20) + 2, - first_blood=False, flag='ractf{}', score=score, correct=True) - solves_to_create.append(solve) - user2.points += points - team.points += points - user.leaderboard_points += points - team.leaderboard_points += points - - teams_to_update.append(team) - users_to_update.append(user) - users_to_update.append(user2) - - timed_log(lambda: Solve.objects.bulk_create(solves_to_create), "[1/4] Saving Solves in database...") - timed_log(lambda: Member.objects.bulk_update(members_to_update, ["leaderboard_points"]), "[2/4] Saving Members in database...") - timed_log(lambda: Team.objects.bulk_update(teams_to_update, ["leaderboard_points"]), "[3/4] Saving Teams in database...") - timed_log(lambda: Score.objects.bulk_create(scores_to_create), "[4/4] Saving Scores in database...") + with TimedLog("Inserting phat dummy data... ", ending="\n"): + with TimedLog("Creating 10 categories..."): + for i in range(10): + category = Category(name='category-' + str(i), display_order=i, contained_type='test', + description='Category ' + str(i)) + category.save() + + def random_rpn_op(depth=0): + depth += 1 + + if depth > 4 or (random.randint(1, 4) < 3 and depth > 1): + return str(random.randint(1, 1000)) + + if random.randint(1, 2) == 1: + return f"{random_rpn_op(depth)} {random_rpn_op(depth)} OR" + else: + return f"{random_rpn_op(depth)} {random_rpn_op(depth)} AND" + + with TimedLog('Creating 100 challenges for each category...'): + for i in range(10): + category = Category.objects.get(name='category-' + str(i)) + for j in range(100): + auto_unlock = (random.randint(1, 5) == 1) + challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, + description='An example challenge ' + str(j), + flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', + auto_unlock=auto_unlock, score=j, challenge_metadata={}, + unlock_requirements=random_rpn_op() if not auto_unlock else "") + challenge.save() + + with TimedLog("Creating 20k users in memory..."): + Member = get_user_model() + users_to_create = [] + for i in range(10000): + user = Member(username='user-' + str(i), email='user-' + str(i) + '@example.org') + user2 = Member(username='user-' + str(i) + '-second', + email='user-' + str(i) + '-second@example.org') + users_to_create.append(user) + users_to_create.append(user2) + + with TimedLog("Inserting to database..."): + Member.objects.bulk_create(users_to_create) + + with TimedLog("Creating 10k teams in memory...."): + teams_to_create = [] + members = list(Member.objects.all()) + for i in range(10000): + team = Team(name='team-' + str(i), password='password', owner=members[i * 2]) + + teams_to_create.append(team) + + with TimedLog("Inserting to database..."): + Team.objects.bulk_create(teams_to_create) + + with TimedLog("Adding members to teams in memory..."): + members_to_update = [] + teams = list(Team.objects.all()) + for i in range(0, len(members), 2): + owner = members[i] + teammate = members[i+1] + + owner.team = teams[i // 2] + teammate.team = teams[i // 2] + + members_to_update.append(owner) + members_to_update.append(teammate) + + with TimedLog("Saving to database..."): + Member.objects.bulk_update(members_to_update, ["team"]) + + with TimedLog("Creating 1m solves and scores in memory..."): + scores_to_create = [] + solves_to_create = [] + users_to_update = [] + teams_to_update = [] + for team in Team.objects.prefetch_related("members").all(): + z = team.members.all() + user = z[0] + user2 = z[1] + used = [] + for j in range(50): + points = random.randint(0, 999) + score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) + scores_to_create.append(score) + solve = Solve(team=team, solved_by=user, challenge_id=(j * 19) + (team.id % 20) + 1, first_blood=False, + flag='ractf{}', score=score, correct=True) + solves_to_create.append(solve) + user.points += points + team.points += points + user.leaderboard_points += points + team.leaderboard_points += points + + points = random.randint(0, 999) + score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) + scores_to_create.append(score) + solve = Solve(team=team, solved_by=user2, challenge_id=(j * 19) + (team.id % 20) + 2, + first_blood=False, flag='ractf{}', score=score, correct=True) + solves_to_create.append(solve) + user2.points += points + team.points += points + user.leaderboard_points += points + team.leaderboard_points += points + + teams_to_update.append(team) + users_to_update.append(user) + users_to_update.append(user2) + + with TimedLog("Saving all to database...", ending="\n"): + with TimedLog("[1/4] Saving Solves in database..."): + Solve.objects.bulk_create(solves_to_create) + with TimedLog("[2/4] Saving Members in database..."): + Member.objects.bulk_update(members_to_update, ["leaderboard_points"]) + with TimedLog("[3/4] Saving Teams in database..."): + Team.objects.bulk_update(teams_to_update, ["leaderboard_points"]) + with TimedLog("[4/4] Saving Scores in database..."): + Score.objects.bulk_create(scores_to_create) From 2bcfba3e817a07eb6bf646c9f8108a8ecf9f7c37 Mon Sep 17 00:00:00 2001 From: bentechy Date: Thu, 8 Apr 2021 18:05:01 +0100 Subject: [PATCH 009/185] remove auto_unlock, move some query logic into python --- .../migrations/0016_auto_20210408_1804.py | 22 ++++++++++++ src/challenge/models.py | 36 +++++++------------ src/challenge/serializers.py | 25 ++++++++----- src/challenge/views.py | 15 +------- src/leaderboard/tests.py | 2 +- .../commands/insert_mega_dummy_data.py | 2 +- 6 files changed, 54 insertions(+), 48 deletions(-) create mode 100644 src/challenge/migrations/0016_auto_20210408_1804.py diff --git a/src/challenge/migrations/0016_auto_20210408_1804.py b/src/challenge/migrations/0016_auto_20210408_1804.py new file mode 100644 index 00000000..b4344610 --- /dev/null +++ b/src/challenge/migrations/0016_auto_20210408_1804.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1 on 2021-04-08 17:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('challenge', '0015_auto_20210131_1809'), + ] + + operations = [ + migrations.RemoveField( + model_name='challenge', + name='auto_unlock', + ), + migrations.AlterField( + model_name='challenge', + name='unlock_requirements', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/src/challenge/models.py b/src/challenge/models.py index cb8ae87c..2f9a4054 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -45,7 +45,6 @@ class Challenge(ExportModelOperationsMixin("challenge"), models.Model): flag_type = models.CharField(max_length=64, default="plaintext") flag_metadata = JSONField() author = models.CharField(max_length=36) - auto_unlock = models.BooleanField(default=False) hidden = models.BooleanField(default=False) score = models.IntegerField() unlock_requirements = models.CharField(max_length=255, null=True, blank=True) @@ -60,12 +59,12 @@ class Challenge(ExportModelOperationsMixin("challenge"), models.Model): release_time = models.DateTimeField(default=timezone.now) def is_unlocked(self, user, solves=None): + if not self.unlock_requirements: + return True if user is None: return False if not user.is_authenticated: return False - if self.auto_unlock: - return True if user.team is None: return False if solves is None: @@ -91,51 +90,42 @@ def is_unlocked(self, user, solves=None): return False return state[0] - def is_solved(self, user): + def is_solved(self, user, solves=None): if not user.is_authenticated: return False if user.team is None: return False - return user.team.solves.filter(challenge=self).exists() + if solves is None: + solves = list( + user.team.solves.filter(correct=True).values_list("challenge", flat=True) + ) + return self.id in solves + + def get_solve_count(self, solve_counter): + return solve_counter[self.id] @classmethod def get_unlocked_annotated_queryset(cls, user): if user.is_staff and user.should_deny_admin(): return Challenge.objects.none() if user.team is not None: - solves = Solve.objects.filter(team=user.team, correct=True) - solved_challenges = solves.values_list("challenge") challenges = Challenge.objects.annotate( - solved=Case( - When(Q(id__in=Subquery(solved_challenges)), then=Value(True)), - default=Value(False), - output_field=models.BooleanField(), - ), solve_count=Count("solves", filter=Q(solves__correct=True)), unlock_time_surpassed=Case( When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ), - votes_positive=Count("votes", filter=Q(votes__positive=True), distinct=True), - votes_negative=Count("votes", filter=Q(votes__positive=False), distinct=True), + ) ) else: challenges = Challenge.objects.annotate( - unlocked=Case( - When(auto_unlock=True, then=Value(True)), - default=Value(False), - output_field=models.BooleanField(), - ), solved=Value(False, models.BooleanField()), solve_count=Count("solves"), unlock_time_surpassed=Case( When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ), - votes_positive=Count("votes", filter=Q(votes__positive=True), distinct=True), - votes_negative=Count("votes", filter=Q(votes__positive=False), distinct=True), + ) ) from hint.models import Hint from hint.models import HintUse diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 1259d121..f7f327ad 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,6 +1,8 @@ +from collections import Counter + from rest_framework import serializers -from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag +from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote from hint.serializers import HintSerializer @@ -23,22 +25,24 @@ def get_unlocked(self, instance): return instance.unlocked def get_solved(self, instance): + if not getattr(instance, "solved", None): + return instance.is_solved(self.context["request"].user, solves=self.context.get("solves", None)) return instance.solved def get_solve_count(self, instance): - return instance.solve_count + return instance.get_solve_count(self.context.get("solve_counter", None)) def get_unlock_time_surpassed(self, instance): return instance.unlock_time_surpassed def get_votes(self, instance): return { - "positive": instance.votes_positive, - "negative": instance.votes_negative + "positive": self.context["votes_positive_counter"][instance.id], + "negative": self.context["votes_negative_counter"][instance.id] } def get_post_score_explanation(self, instance): - if instance.solved: + if self.get_unlocked(instance): return instance.post_score_explanation return None @@ -67,7 +71,7 @@ class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer) class Meta: model = Challenge fields = ['id', 'name', 'category', 'description', 'challenge_type', 'challenge_metadata', 'flag_type', - 'author', 'auto_unlock', 'score', 'unlock_requirements', 'hints', 'files', 'solved', 'unlocked', + 'author', 'score', 'unlock_requirements', 'hints', 'files', 'solved', 'unlocked', 'first_blood', 'first_blood_name', 'solve_count', 'hidden', 'votes', 'tags', 'unlock_time_surpassed', 'post_score_explanation'] @@ -91,7 +95,10 @@ def __init__(self, *args, **kwargs): "request": self.context["request"], "solves": list( self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ) + ), + "solve_counter": Counter(Solve.objects.filter(correct=True).values_list("challenge", flat=True)), + "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), + "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), }) @@ -125,7 +132,7 @@ class AdminChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerial class Meta: model = Challenge fields = ['id', 'name', 'category', 'description', 'challenge_type', 'challenge_metadata', 'flag_type', - 'author', 'auto_unlock', 'score', 'unlock_requirements', 'flag_metadata', 'hints', 'files', 'solved', + 'author', 'score', 'unlock_requirements', 'flag_metadata', 'hints', 'files', 'solved', 'unlocked', 'first_blood', 'first_blood_name', 'solve_count', 'hidden', 'release_time', 'votes', 'post_score_explanation', 'tags'] @@ -136,7 +143,7 @@ class CreateChallengeSerializer(serializers.ModelSerializer): class Meta: model = Challenge fields = ['id', 'name', 'category', 'description', 'challenge_type', 'challenge_metadata', 'flag_type', - 'author', 'auto_unlock', 'score', 'unlock_requirements', 'flag_metadata', 'hidden', 'release_time', + 'author', 'score', 'unlock_requirements', 'flag_metadata', 'hidden', 'release_time', 'post_score_explanation', 'tags'] read_only_fields = ['id'] diff --git a/src/challenge/views.py b/src/challenge/views.py index eb01c3e6..c763afc3 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -1,6 +1,7 @@ import time import hashlib from typing import Union +from collections import Counter from django.conf import settings from django.contrib.auth import get_user_model @@ -51,15 +52,7 @@ def get_queryset(self): return Category.objects.none() team = self.request.user.team if team is not None: - solves = Solve.objects.filter(team=team, correct=True) - solved_challenges = solves.values_list('challenge') challenges = Challenge.objects.annotate( - solved=Case( - When(Q(id__in=Subquery(solved_challenges)), then=Value(True)), - default=Value(False), - output_field=models.BooleanField() - ), - solve_count=Count('solves', filter=Q(solves__correct=True)), unlock_time_surpassed=Case( When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), @@ -71,13 +64,7 @@ def get_queryset(self): else: challenges = ( Challenge.objects.annotate( - unlocked=Case( - When(auto_unlock=True, then=Value(True)), - default=Value(False), - output_field=models.BooleanField() - ), solved=Value(False, models.BooleanField()), - solve_count=Count('solves'), unlock_time_surpassed=Case( When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 1e621594..3d9b83d4 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -14,7 +14,7 @@ def populate(): category.save() challenge = Challenge(name='test3', category=category, description='a', challenge_type='basic', challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='aaa', score=1000, auto_unlock=False) + author='aaa', score=1000, unlock_requirements="") challenge.save() for i in range(15): user = get_user_model()(username=f'scorelist-test{i}', email=f'scorelist-test{i}@example.org', is_visible=True) diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 18831140..9211c514 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -51,7 +51,7 @@ def random_rpn_op(depth=0): challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, description='An example challenge ' + str(j), flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', - auto_unlock=auto_unlock, score=j, challenge_metadata={}, + score=j, challenge_metadata={}, unlock_requirements=random_rpn_op() if not auto_unlock else "") challenge.save() From a1ff37285653645b5c1525c6446880f776bd0d2c Mon Sep 17 00:00:00 2001 From: bentechy Date: Thu, 8 Apr 2021 22:18:48 +0100 Subject: [PATCH 010/185] stonking optimisation to challenges --- src/challenge/serializers.py | 104 ++++++++++++++++++++++++++++++++--- src/challenge/views.py | 12 ++-- src/hint/serializers.py | 31 +++++++---- 3 files changed, 121 insertions(+), 26 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index f7f327ad..8701e413 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,9 +1,22 @@ from collections import Counter +import serpy from rest_framework import serializers from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote -from hint.serializers import HintSerializer +from hint.serializers import HintSerializer, FastHintSerializer + + +class ForeignKeyField(serpy.Field): + """A :class:`Field` that gets a given attribute from a foreign object.""" + def __init__(self, *args, attr_name="id", **kwargs): + super(ForeignKeyField, self).__init__(*args, **kwargs) + self.attr_name = attr_name + + def to_value(self, value): + if value: + return getattr(value, self.attr_name) + return None class FileSerializer(serializers.ModelSerializer): @@ -12,12 +25,26 @@ class Meta: fields = ['id', 'name', 'url', 'size', 'challenge', 'md5'] +class FastFileSerializer(serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + url = serpy.StrField() + size = serpy.IntField() + md5 = serpy.StrField() + challenge = ForeignKeyField() + + class NestedTagSerializer(serializers.ModelSerializer): class Meta: model = Tag fields = ['text', 'type'] +class FastNestedTagSerializer(serpy.Serializer): + text = serpy.StrField() + type = serpy.StrField() + + class ChallengeSerializerMixin: def get_unlocked(self, instance): if not getattr(instance, "unlocked", None): @@ -56,6 +83,39 @@ class Meta: 'unlock_time_surpassed', 'release_time'] +class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + description = serpy.StrField() + challenge_type = serpy.StrField() + flag_type = serpy.StrField() + author = serpy.StrField() + score = serpy.IntField() + unlock_requirements = serpy.StrField() + hints = FastHintSerializer(many=True) + files = FastFileSerializer(many=True) + solved = serpy.MethodField() + unlocked = serpy.MethodField() + first_blood = ForeignKeyField() + solve_count = serpy.MethodField() + hidden = serpy.BoolField() + votes = serpy.MethodField() + tags = FastNestedTagSerializer(many=True) + unlock_time_surpassed = serpy.MethodField() + post_score_explanation = serpy.StrField() + + def __init__(self, *args, **kwargs): + super(FastChallengeSerializer, self).__init__(*args, **kwargs) + if 'context' in kwargs: + self.context = kwargs['context'] + + def to_representation(self, instance): + if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ + not instance.hidden and instance.unlock_time_surpassed: + return super(FastChallengeSerializer, self).to_representation(instance) + return LockedChallengeSerializer(instance).to_representation(instance) + + class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): hints = HintSerializer(many=True, read_only=True) files = FileSerializer(many=True, read_only=True) @@ -75,15 +135,9 @@ class Meta: 'first_blood', 'first_blood_name', 'solve_count', 'hidden', 'votes', 'tags', 'unlock_time_surpassed', 'post_score_explanation'] - def to_representation(self, instance): - if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ - not instance.hidden and instance.unlock_time_surpassed: - return super(ChallengeSerializer, self).to_representation(instance) - return LockedChallengeSerializer(instance).to_representation(instance) - class CategorySerializer(serializers.ModelSerializer): - challenges = ChallengeSerializer(many=True, read_only=True) + challenges = serializers.SerializerMethodField() class Meta: model = Category @@ -101,6 +155,40 @@ def __init__(self, *args, **kwargs): "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), }) + def get_challenges(self, instance): + return FastChallengeSerializer(instance.challenges, many=True, context=self.context).data + + +class FastCategorySerializer(serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + display_order = serpy.StrField() + contained_type = serpy.StrField() + description = serpy.StrField() + metadata = serpy.DictSerializer() + challenges = serpy.MethodField() + + class Meta: + model = Category + fields = ['id', 'name', 'display_order', 'contained_type', 'description', 'metadata', 'challenges'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if "context" in kwargs: + self.context = kwargs["context"] + self.context.update({ + "request": self.context["request"], + "solves": list( + self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) + ), + "solve_counter": Counter(Solve.objects.filter(correct=True).values_list("challenge", flat=True)), + "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), + "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), + }) + + def get_challenges(self, instance): + return FastChallengeSerializer(instance.challenges, many=True, context=self.context).data + class ChallengeFeedbackSerializer(serializers.ModelSerializer): class Meta: diff --git a/src/challenge/views.py b/src/challenge/views.py index c763afc3..27dcb82a 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -29,7 +29,7 @@ ChallengeSerializer, CategorySerializer, AdminCategorySerializer, AdminChallengeSerializer, FileSerializer, CreateCategorySerializer, CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, - AdminScoreSerializer + AdminScoreSerializer, FastCategorySerializer ) from config import config from hint.models import Hint, HintUse @@ -43,7 +43,7 @@ class CategoryViewset(AdminCreateModelViewSet): permission_classes = (CompetitionOpen & AdminOrReadOnly,) throttle_scope = 'challenges' pagination_class = None - serializer_class = CategorySerializer + serializer_class = FastCategorySerializer admin_serializer_class = AdminCategorySerializer create_serializer_class = CreateCategorySerializer @@ -57,9 +57,7 @@ def get_queryset(self): When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ), - votes_positive=Count("votes", filter=Q(votes__positive=True), distinct=True), - votes_negative=Count("votes", filter=Q(votes__positive=False), distinct=True), + ) ) else: challenges = ( @@ -69,9 +67,7 @@ def get_queryset(self): When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ), - votes_positive=Count("votes", filter=Q(votes__positive=True), distinct=True), - votes_negative=Count("votes", filter=Q(votes__positive=False), distinct=True), + ) ) ) x = challenges.prefetch_related( diff --git a/src/hint/serializers.py b/src/hint/serializers.py index 09dc4a89..c698b7e2 100644 --- a/src/hint/serializers.py +++ b/src/hint/serializers.py @@ -1,3 +1,4 @@ +import serpy from rest_framework import serializers from hint.models import Hint, HintUse @@ -16,18 +17,11 @@ class Meta: fields = ["id", "hint", "team", "user", "timestamp"] -class HintSerializer(serializers.ModelSerializer): - text = serializers.SerializerMethodField() - used = serializers.SerializerMethodField() - - class Meta: - model = Hint - fields = ["id", "name", "penalty", "challenge", "text", "used"] - +class HintSerializerMixin: def get_text(self, instance): if ( - self.context["request"].user.is_staff - and not self.context["request"].user.should_deny_admin() + self.context["request"].user.is_staff + and not self.context["request"].user.should_deny_admin() ) or is_used(self.context, instance): return instance.text else: @@ -37,6 +31,23 @@ def get_used(self, instance): return is_used(self.context, instance) +class HintSerializer(serializers.ModelSerializer, HintSerializerMixin): + text = serializers.SerializerMethodField() + used = serializers.SerializerMethodField() + + class Meta: + model = Hint + fields = ["id", "name", "penalty", "challenge", "text", "used"] + + +class FastHintSerializer(serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + penalty = serpy.IntField() + text = serpy.StrField() + used = serpy.BoolField() + + class CreateHintSerializer(serializers.ModelSerializer): class Meta: model = Hint From 768f64a369d24328784d0eff15eccd1c65e1cba7 Mon Sep 17 00:00:00 2001 From: bentechy Date: Thu, 8 Apr 2021 22:39:27 +0100 Subject: [PATCH 011/185] =?UTF-8?q?haha=20locked=20challenges=20exist=20i?= =?UTF-8?q?=20totally=20didnt=20forget=20those=20exist=20=F0=9F=A4=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/challenge/serializers.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 8701e413..68c91545 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -19,6 +19,12 @@ def to_value(self, value): return None +class DateTimeField(serpy.Field): + """A :class:`Field` that transforms a datetime into ISO string.""" + def to_value(self, value): + return value.isoformat() + + class FileSerializer(serializers.ModelSerializer): class Meta: model = File @@ -74,6 +80,20 @@ def get_post_score_explanation(self, instance): return None +class FastLockedChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): + id = serpy.IntField() + unlock_requirements = serpy.StrField() + challenge_metadata = serpy.DictSerializer() + challenge_type = serpy.StrField() + release_time = DateTimeField() + unlock_time_surpassed = serpy.MethodField() + + class Meta: + model = Challenge + fields = ['id', 'unlock_requirements', 'challenge_metadata', 'challenge_type', 'hidden', + 'unlock_time_surpassed', 'release_time'] + + class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): unlock_time_surpassed = serializers.SerializerMethodField() @@ -109,11 +129,16 @@ def __init__(self, *args, **kwargs): if 'context' in kwargs: self.context = kwargs['context'] - def to_representation(self, instance): + def serialize(self, instance): if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ not instance.hidden and instance.unlock_time_surpassed: - return super(FastChallengeSerializer, self).to_representation(instance) - return LockedChallengeSerializer(instance).to_representation(instance) + return super(FastChallengeSerializer, FastChallengeSerializer(instance, context=self.context)).to_value(instance) + return FastLockedChallengeSerializer(instance).data + + def to_value(self, instance): + if self.many: + return [self.serialize(o) for o in instance] + return self.serialize(instance) class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): From 852e29963599920e067a5d7659c3d23bfae96cb8 Mon Sep 17 00:00:00 2001 From: bentechy Date: Sat, 10 Apr 2021 13:37:56 +0100 Subject: [PATCH 012/185] remove one query from every pageload lol --- src/backend/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/authentication.py b/src/backend/authentication.py index b38a4d2f..b4a789d7 100644 --- a/src/backend/authentication.py +++ b/src/backend/authentication.py @@ -18,7 +18,7 @@ def authenticate(self, request): return None if not config.get("enable_bot_users") and user.is_bot: return None - if token.user != token.owner: + if token.user_id != token.owner_id: request.sudo = True request.sudo_from = token.owner return user, token From b59bd4dc3ce5e3d69150497a6be356b45a0a5c79 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 03:11:57 +0100 Subject: [PATCH 013/185] proof of concept custom serialization --- poetry.lock | 85 +++++++++---- pyproject.toml | 1 + src/challenge/serializers.py | 4 +- src/challenge/views.py | 118 ++++++++++++++++-- .../management/commands/insert_dummy_data.py | 4 +- 5 files changed, 175 insertions(+), 37 deletions(-) diff --git a/poetry.lock b/poetry.lock index 69719282..678e7aad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,6 +171,19 @@ python-dateutil = ">=2.1,<3.0.0" python = "<3.4.0 || >=3.5.0" version = ">=1.25.4,<1.27" +[[package]] +category = "main" +description = "Minimal persistent memoization cache" +name = "cachalot" +optional = false +python-versions = ">=3.5,<4.0" +version = "1.5.0" + +[package.dependencies] +jsonpickle = "*" +tinydb = ">=4,<5" +tinydb-smartcache = "*" + [[package]] category = "main" description = "Python package for providing Mozilla's CA Bundle." @@ -661,6 +674,19 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.10.0" +[[package]] +category = "main" +description = "Python library for serializing any arbitrary object graph into JSON" +name = "jsonpickle" +optional = false +python-versions = ">=2.7" +version = "2.0.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["coverage (<5)", "pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] +"testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] + [[package]] category = "main" description = "Safely add untrusted strings to HTML/XML markup." @@ -1024,6 +1050,25 @@ optional = false python-versions = "*" version = "1.1.0" +[[package]] +category = "main" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" +name = "tinydb" +optional = false +python-versions = ">=3.5,<4.0" +version = "4.4.0" + +[[package]] +category = "main" +description = "A smarter query cache for TinyDB" +name = "tinydb-smartcache" +optional = false +python-versions = ">=3.5,<4.0" +version = "2.0.0" + +[package.dependencies] +tinydb = ">=4.0,<5.0" + [[package]] category = "main" description = "Python Library for Tom's Obvious, Minimal Language" @@ -1181,7 +1226,7 @@ python-versions = "*" version = "4.4.28" [metadata] -content-hash = "6ad40bd4a1528ccc0d539f97248fe590d9c258abcd91da2e4d33187ccedc1338" +content-hash = "2f533a625e2d7574d952a7918aa3954cade5a9f81b3dbbf3b45b4849933fdb47" lock-version = "1.0" python-versions = "^3.8" @@ -1236,6 +1281,10 @@ botocore = [ {file = "botocore-1.19.43-py2.py3-none-any.whl", hash = "sha256:795a67338cadb0c3a45014a6c81659da6af623a4e973812f87a6f9d9fb7712e9"}, {file = "botocore-1.19.43.tar.gz", hash = "sha256:7398c900dbd4e3d61647269215396ea3e8082f494f3e7b65d9b6aca049c1d463"}, ] +cachalot = [ + {file = "Cachalot-1.5.0-py3-none-any.whl", hash = "sha256:426a42d966c624b3524d53aaabf1438373168f78b2f1123283fdb20470a37432"}, + {file = "Cachalot-1.5.0.tar.gz", hash = "sha256:2dac05118e746f3eb9aea7de20327610b6619e41cb145cb5bfd9098e7f901c43"}, +] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, @@ -1527,6 +1576,10 @@ jmespath = [ {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, ] +jsonpickle = [ + {file = "jsonpickle-2.0.0-py2.py3-none-any.whl", hash = "sha256:c1010994c1fbda87a48f8a56698605b598cb0fc6bb7e7927559fc1100e69aeac"}, + {file = "jsonpickle-2.0.0.tar.gz", hash = "sha256:0be49cba80ea6f87a168aa8168d717d00c6ca07ba83df3cec32d3b30bfe6fb9a"}, +] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, @@ -1546,39 +1599,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] msgpack = [ @@ -1694,11 +1728,8 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, ] ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, @@ -1874,6 +1905,14 @@ sqlparse = [ starkbank-ecdsa = [ {file = "starkbank-ecdsa-1.1.0.tar.gz", hash = "sha256:423f81bb55c896a3c85ee98ac7da98826721eaee918f5c0c1dfff99e1972da0c"}, ] +tinydb = [ + {file = "tinydb-4.4.0-py3-none-any.whl", hash = "sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71"}, + {file = "tinydb-4.4.0.tar.gz", hash = "sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a"}, +] +tinydb-smartcache = [ + {file = "tinydb-smartcache-2.0.0.tar.gz", hash = "sha256:a1f6034d8fe28a72810fdbef46a0547949cbf7e9b43a69343dc0e1adfb47f04a"}, + {file = "tinydb_smartcache-2.0.0-py3-none-any.whl", hash = "sha256:c162fe6c558bb73ff1e6b33b168807ea87063e47e2b85cb9e84c12d4354aa7c8"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, diff --git a/pyproject.toml b/pyproject.toml index 286fee99..c7d8192f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ sendgrid = "^6.4.7" newrelic = "^5.22.1" django-prometheus = "^2.1.0" django-cachalot = "^2.3.5" +cachalot = "^1.5.0" [tool.poetry.dev-dependencies] ipython = "^7.19.0" diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 68c91545..6442a0fc 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -309,12 +309,12 @@ def to_representation(self, instance): return None def get_points(self, instance): - if instance.correct: + if instance.correct and instance.score is not None: return instance.score.points - instance.score.penalty return 0 def get_scored(self, instance): - if instance.correct: + if instance.correct and instance.score is not None: return instance.score.leaderboard return False diff --git a/src/challenge/views.py b/src/challenge/views.py index 27dcb82a..2fbad3b8 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -1,14 +1,14 @@ -import time import hashlib -from typing import Union +import time from collections import Counter +from typing import Union from django.conf import settings from django.contrib.auth import get_user_model -from django.db import transaction, models -from django.db.models import Prefetch, Case, When, Value, Count, Subquery, Q, Sum +from django.core.cache import caches +from django.db import transaction, models, connection +from django.db.models import Prefetch, Case, When, Value, Sum from django.utils import timezone - from rest_framework import permissions from rest_framework.generics import get_object_or_404 from rest_framework.parsers import MultiPartParser @@ -26,7 +26,7 @@ from challenge.models import Challenge, Category, Solve, File, ChallengeVote, ChallengeFeedback, Tag, Score from challenge.permissions import CompetitionOpen from challenge.serializers import ( - ChallengeSerializer, CategorySerializer, AdminCategorySerializer, + ChallengeSerializer, AdminCategorySerializer, AdminChallengeSerializer, FileSerializer, CreateCategorySerializer, CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, AdminScoreSerializer, FastCategorySerializer @@ -38,6 +38,84 @@ from team.permissions import HasTeam +def get_cache_key(user): + if user.team is None: + return 'categoryvs_user_' + str(user.id) + else: + return 'categoryvs_team_' + str(user.team.id) + + +def serialize_categories(categories, context): + solve_counts = {} + with connection.cursor() as cursor: + cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + for row in cursor.fetchall(): + solve_counts[row[0]] = row[1] + serialized = [] + for category in categories: + serialized_category = { + 'id': category.id, + 'name': category.name, + 'display_order': category.display_order, + 'contained_type': category.contained_type, + 'description': category.description, + 'metadata': category.metadata, + 'challenges': [] + } + for challenge in category.challenges: + serialized_challenge = { + 'id': challenge.id, + 'name': challenge.name, + 'description': challenge.description, + 'challenge_type': challenge.challenge_type, + 'flag_type': challenge.flag_type, + 'author': challenge.author, + 'score': challenge.score, + 'unlock_requirements': challenge.unlock_requirements, + 'hints': [], + 'files': [], + 'solved': challenge.is_solved(context["request"].user, solves=context.get("solves", None)), + 'unlocked': challenge.is_unlocked(context["request"].user, solves=context.get("solves", None)), + 'first_blood': challenge.first_blood_id, + 'solve_count': solve_counts.get(challenge.id, 0), + 'hidden': challenge.hidden, + 'votes': { + "positive": context["votes_positive_counter"][challenge.id], + "negative": context["votes_negative_counter"][challenge.id] + }, + 'tags': [], + 'unlock_time_surpassed': challenge.unlock_time_surpassed, + 'post_score_explanation': challenge.post_score_explanation, + } + if serialized_challenge['solved'] is None: + serialized_challenge['solved'] = challenge.is_solved(context["request"].user, + solves=context.get("solves", None)) + for hint in challenge.hints: + serialized_challenge['hints'].append({ + 'id': hint.id, + 'name': hint.name, + 'penalty': hint.penalty, + 'text': hint.text, + 'used': hint.used + }) + for file in challenge.files: + serialized_challenge['files'].append({ + 'id': file.id, + 'name': file.name, + 'url': file.url, + 'size': file.size, + }) + for tag in challenge.tags: + serialized_challenge['tags'].append({ + 'type': tag.type, + 'text': tag.text, + }) + + serialized_category['challenges'].append(serialized_challenge) + serialized.append(serialized_category) + return serialized + + class CategoryViewset(AdminCreateModelViewSet): queryset = Category.objects.all() permission_classes = (CompetitionOpen & AdminOrReadOnly,) @@ -92,9 +170,26 @@ def get_queryset(self): return qs def list(self, request, *args, **kwargs): + cache = caches['default'] + category_data = cache.get(get_cache_key(request.user)) + if category_data is None: + queryset = self.filter_queryset(self.get_queryset()) + category_data = list(queryset) + cache.set(get_cache_key(request.user), category_data, 30) + #serializer = self.get_serializer(category_data, many=True) + categories = serialize_categories(category_data, { + "request": request, + "solves": list( + request.user.team.solves.filter(correct=True).values_list("challenge", flat=True) + ), + "votes_positive_counter": Counter( + ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), + "votes_negative_counter": Counter( + ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), + }) + # This is to fix an issue with django duplicating challenges on .annotate. # If you want to clean this up, good luck. - categories = super(CategoryViewset, self).list(request, *args, **kwargs).data for category in categories: unlocked = set() for challenge in category['challenges']: @@ -131,13 +226,15 @@ class ScoresViewset(ModelViewSet): def recalculate_scores(self, user, team): if user: user = get_object_or_404(get_user_model(), id=user) - user.leaderboard_points = Score.objects.filter(user=user, leaderboard=True).aggregate(Sum("points"))["points__sum"] or 0 + user.leaderboard_points = Score.objects.filter(user=user, leaderboard=True).aggregate(Sum("points"))[ + "points__sum"] or 0 user.points = Score.objects.filter(user=user).aggregate(Sum("points"))["points__sum"] or 0 user.last_score = Score.objects.filter(user=user, leaderboard=True).order_by("timestamp").first().timestamp user.save() if team: team = get_object_or_404(Team, id=team) - team.leaderboard_points = Score.objects.filter(team=team, leaderboard=True).aggregate(Sum("points"))["points__sum"] or 0 + team.leaderboard_points = Score.objects.filter(team=team, leaderboard=True).aggregate(Sum("points"))[ + "points__sum"] or 0 team.points = Score.objects.filter(team=team).aggregate(Sum("points"))["points__sum"] or 0 team.last_score = Score.objects.filter(team=team, leaderboard=True).order_by("timestamp").first().timestamp team.save() @@ -311,7 +408,8 @@ def create(self, request: Request, *args, **kwargs) -> Union[FormattedResponse, if file_data: if len(file_data) > settings.MAX_UPLOAD_SIZE: - return FormattedResponse(m=f"File cannot be over {settings.MAX_UPLOAD_SIZE} bytes in size.", status=HTTP_400_BAD_REQUEST) + return FormattedResponse(m=f"File cannot be over {settings.MAX_UPLOAD_SIZE} bytes in size.", + status=HTTP_400_BAD_REQUEST) file = File(challenge=challenge, upload=file_data) file.name = file.upload.name file.size = file.upload.size diff --git a/src/ractf/management/commands/insert_dummy_data.py b/src/ractf/management/commands/insert_dummy_data.py index cbcee713..81ccdba6 100644 --- a/src/ractf/management/commands/insert_dummy_data.py +++ b/src/ractf/management/commands/insert_dummy_data.py @@ -16,10 +16,10 @@ def handle(self, *args, **options): author='dave', score=1000) challenge2 = Challenge(name='test2', category=category, description='a', challenge_type='basic', challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000, auto_unlock=True) + author='dave', score=1000) challenge3 = Challenge(name='test3', category=category, description='a', challenge_type='basic', challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000, auto_unlock=False) + author='dave', score=1000) challenge1.save() challenge2.save() challenge3.save() From 1ed429b8d7a836ed4b56fef217c3db11bebeb1d7 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 03:48:30 +0100 Subject: [PATCH 014/185] revert to serpy with solve count optimisation --- src/challenge/models.py | 2 +- src/challenge/serializers.py | 13 +++++- src/challenge/views.py | 86 ++---------------------------------- 3 files changed, 15 insertions(+), 86 deletions(-) diff --git a/src/challenge/models.py b/src/challenge/models.py index 2f9a4054..0f303e2d 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -102,7 +102,7 @@ def is_solved(self, user, solves=None): return self.id in solves def get_solve_count(self, solve_counter): - return solve_counter[self.id] + return solve_counter.get(self.id, 0) @classmethod def get_unlocked_annotated_queryset(cls, user): diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 6442a0fc..945ab4fd 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,6 +1,7 @@ from collections import Counter import serpy +from django.db import connection from rest_framework import serializers from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote @@ -170,12 +171,16 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} self.fields['challenges'].context.update({ "request": self.context["request"], "solves": list( self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) ), - "solve_counter": Counter(Solve.objects.filter(correct=True).values_list("challenge", flat=True)), + "solve_counter": solve_counts, "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), }) @@ -201,12 +206,16 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs: self.context = kwargs["context"] + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} self.context.update({ "request": self.context["request"], "solves": list( self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) ), - "solve_counter": Counter(Solve.objects.filter(correct=True).values_list("challenge", flat=True)), + "solve_counter": solve_counts, "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), }) diff --git a/src/challenge/views.py b/src/challenge/views.py index 2fbad3b8..313089b8 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -40,82 +40,11 @@ def get_cache_key(user): if user.team is None: - return 'categoryvs_user_' + str(user.id) + return 'categoryvs_no_team' else: return 'categoryvs_team_' + str(user.team.id) -def serialize_categories(categories, context): - solve_counts = {} - with connection.cursor() as cursor: - cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') - for row in cursor.fetchall(): - solve_counts[row[0]] = row[1] - serialized = [] - for category in categories: - serialized_category = { - 'id': category.id, - 'name': category.name, - 'display_order': category.display_order, - 'contained_type': category.contained_type, - 'description': category.description, - 'metadata': category.metadata, - 'challenges': [] - } - for challenge in category.challenges: - serialized_challenge = { - 'id': challenge.id, - 'name': challenge.name, - 'description': challenge.description, - 'challenge_type': challenge.challenge_type, - 'flag_type': challenge.flag_type, - 'author': challenge.author, - 'score': challenge.score, - 'unlock_requirements': challenge.unlock_requirements, - 'hints': [], - 'files': [], - 'solved': challenge.is_solved(context["request"].user, solves=context.get("solves", None)), - 'unlocked': challenge.is_unlocked(context["request"].user, solves=context.get("solves", None)), - 'first_blood': challenge.first_blood_id, - 'solve_count': solve_counts.get(challenge.id, 0), - 'hidden': challenge.hidden, - 'votes': { - "positive": context["votes_positive_counter"][challenge.id], - "negative": context["votes_negative_counter"][challenge.id] - }, - 'tags': [], - 'unlock_time_surpassed': challenge.unlock_time_surpassed, - 'post_score_explanation': challenge.post_score_explanation, - } - if serialized_challenge['solved'] is None: - serialized_challenge['solved'] = challenge.is_solved(context["request"].user, - solves=context.get("solves", None)) - for hint in challenge.hints: - serialized_challenge['hints'].append({ - 'id': hint.id, - 'name': hint.name, - 'penalty': hint.penalty, - 'text': hint.text, - 'used': hint.used - }) - for file in challenge.files: - serialized_challenge['files'].append({ - 'id': file.id, - 'name': file.name, - 'url': file.url, - 'size': file.size, - }) - for tag in challenge.tags: - serialized_challenge['tags'].append({ - 'type': tag.type, - 'text': tag.text, - }) - - serialized_category['challenges'].append(serialized_challenge) - serialized.append(serialized_category) - return serialized - - class CategoryViewset(AdminCreateModelViewSet): queryset = Category.objects.all() permission_classes = (CompetitionOpen & AdminOrReadOnly,) @@ -176,17 +105,8 @@ def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) category_data = list(queryset) cache.set(get_cache_key(request.user), category_data, 30) - #serializer = self.get_serializer(category_data, many=True) - categories = serialize_categories(category_data, { - "request": request, - "solves": list( - request.user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ), - "votes_positive_counter": Counter( - ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), - "votes_negative_counter": Counter( - ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), - }) + serializer = self.get_serializer(category_data, many=True) + categories = serializer.data # This is to fix an issue with django duplicating challenges on .annotate. # If you want to clean this up, good luck. From 220bb7fa0ce46a72fa67c72835f96c64fa112072 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 03:58:33 +0100 Subject: [PATCH 015/185] solve counts go brrrrr --- src/challenge/serializers.py | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 945ab4fd..559b63fe 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -8,6 +8,30 @@ from hint.serializers import HintSerializer, FastHintSerializer +def get_solve_counts(): + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + return solve_counts + + +def get_positive_votes(): + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + return solve_counts + + +def get_negative_votes(): + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + return solve_counts + + class ForeignKeyField(serpy.Field): """A :class:`Field` that gets a given attribute from a foreign object.""" def __init__(self, *args, attr_name="id", **kwargs): @@ -71,8 +95,8 @@ def get_unlock_time_surpassed(self, instance): def get_votes(self, instance): return { - "positive": self.context["votes_positive_counter"][instance.id], - "negative": self.context["votes_negative_counter"][instance.id] + "positive": self.context["votes_positive_counter"].get(instance.id, 0), + "negative": self.context["votes_negative_counter"].get(instance.id, 0) } def get_post_score_explanation(self, instance): @@ -206,18 +230,14 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs: self.context = kwargs["context"] - with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} self.context.update({ "request": self.context["request"], "solves": list( self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) ), - "solve_counter": solve_counts, - "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), - "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), }) def get_challenges(self, instance): From 87ff950cb8900322e3b2fdef129380c4f0fb48c8 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 04:00:44 +0100 Subject: [PATCH 016/185] ZOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOM --- src/challenge/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/challenge/views.py b/src/challenge/views.py index 313089b8..64bbbe1a 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -100,13 +100,13 @@ def get_queryset(self): def list(self, request, *args, **kwargs): cache = caches['default'] - category_data = cache.get(get_cache_key(request.user)) - if category_data is None: - queryset = self.filter_queryset(self.get_queryset()) - category_data = list(queryset) - cache.set(get_cache_key(request.user), category_data, 30) - serializer = self.get_serializer(category_data, many=True) - categories = serializer.data + categories = cache.get(get_cache_key(request.user)) + if categories is None: + queryset = self.filter_queryset(self.get_queryset()) + category_data = list(queryset) + serializer = self.get_serializer(category_data, many=True) + categories = serializer.data + cache.set(get_cache_key(request.user), categories, 30) # This is to fix an issue with django duplicating challenges on .annotate. # If you want to clean this up, good luck. From 8893f34340f12c2fcbbab398c50f13e46561cf71 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:15:40 +0100 Subject: [PATCH 017/185] more zoom --- poetry.lock | 6 +++--- pyproject.toml | 2 +- src/backend/settings/__init__.py | 4 ++++ src/challenge/serializers.py | 24 ++++++++++++++++++++---- src/challenge/views.py | 26 +++++++++++++++++++------- src/hint/views.py | 4 +++- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 678e7aad..5efcc220 100644 --- a/poetry.lock +++ b/poetry.lock @@ -443,7 +443,7 @@ description = "Silky smooth profiling for the Django Framework" name = "django-silk" optional = false python-versions = ">=3.5" -version = "4.0.1" +version = "4.1.0" [package.dependencies] Django = ">=2.2" @@ -1226,7 +1226,7 @@ python-versions = "*" version = "4.4.28" [metadata] -content-hash = "2f533a625e2d7574d952a7918aa3954cade5a9f81b3dbbf3b45b4849933fdb47" +content-hash = "4f00dc7966e89a916217e0c953484c9d8ea16a3c4e611cda6b39450c39c58518" lock-version = "1.0" python-versions = "^3.8" @@ -1464,7 +1464,7 @@ django-redis-cache = [ {file = "django_redis_cache-2.1.1-py3-none-any.whl", hash = "sha256:b19ee6654cc2f2c89078c99255e07e19dc2dba8792351d76ba7ea899d465fbb0"}, ] django-silk = [ - {file = "django_silk-4.0.1-py2.py3-none-any.whl", hash = "sha256:56c2c5aefd1c65161df61e49cf2674862a748a43e81c7c470bcbc4c35c53491c"}, + {file = "django_silk-4.1.0-py2.py3-none-any.whl", hash = "sha256:a331e55618fa62eaf3cf5a63f31bc1e91205efbeeca5e587c577498b0e251ed8"}, ] django-storages = [ {file = "django-storages-1.9.1.tar.gz", hash = "sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91"}, diff --git a/pyproject.toml b/pyproject.toml index c7d8192f..950ba9d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ Django = "^3.1" django-cors-headers = "3.2.1" django-redis = "4.11.0" django-redis-cache = "2.1.1" -django-silk = "4.0.1" django-storages = "1.9.1" django-zxcvbn-password-validator = "1.3.0" djangorestframework = "3.11.1" @@ -26,6 +25,7 @@ newrelic = "^5.22.1" django-prometheus = "^2.1.0" django-cachalot = "^2.3.5" cachalot = "^1.5.0" +django-silk = "^4.1.0" [tool.poetry.dev-dependencies] ipython = "^7.19.0" diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index 2575c0e8..d371aeed 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -73,6 +73,7 @@ "storages", "corsheaders", "cachalot", + #"silk", "django_prometheus", "django.contrib.auth", "django.contrib.contenttypes", @@ -91,9 +92,12 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + #'silk.middleware.SilkyMiddleware', "django_prometheus.middleware.PrometheusAfterMiddleware", ] +#SILKY_PYTHON_PROFILER = True + ROOT_URLCONF = "backend.urls" TEMPLATE_DIRS = [ diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 559b63fe..13af0fac 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,6 +1,7 @@ from collections import Counter import serpy +from django.core.cache import caches from django.db import connection from rest_framework import serializers @@ -9,27 +10,42 @@ def get_solve_counts(): + cache = caches['default'] + solve_counts = cache.get('solve_counts') + if solve_counts is not None: + return solve_counts with connection.cursor() as cursor: cursor.execute( 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('solve_counts', solve_counts, 15) return solve_counts def get_positive_votes(): + cache = caches['default'] + positive_votes = cache.get('positive_votes') + if positive_votes is not None: + return positive_votes with connection.cursor() as cursor: cursor.execute( 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - return solve_counts + positive_votes = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('positive_votes', cache.get('positive_votes'), 15) + return positive_votes def get_negative_votes(): + cache = caches['default'] + negative_votes = cache.get('negative_votes') + if negative_votes is not None: + return negative_votes with connection.cursor() as cursor: cursor.execute( 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - return solve_counts + negative_votes = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('negative_votes', cache.get('negative_votes'), 15) + return negative_votes class ForeignKeyField(serpy.Field): diff --git a/src/challenge/views.py b/src/challenge/views.py index 64bbbe1a..3fca27f0 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -1,12 +1,11 @@ import hashlib import time -from collections import Counter from typing import Union from django.conf import settings from django.contrib.auth import get_user_model from django.core.cache import caches -from django.db import transaction, models, connection +from django.db import transaction, models from django.db.models import Prefetch, Case, When, Value, Sum from django.utils import timezone from rest_framework import permissions @@ -29,7 +28,7 @@ ChallengeSerializer, AdminCategorySerializer, AdminChallengeSerializer, FileSerializer, CreateCategorySerializer, CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, - AdminScoreSerializer, FastCategorySerializer + AdminScoreSerializer, FastCategorySerializer, get_solve_counts, get_positive_votes, get_negative_votes ) from config import config from hint.models import Hint, HintUse @@ -88,7 +87,7 @@ def get_queryset(self): Prefetch('tag_set', queryset=Tag.objects.all() if time.time() > config.get('end_time') else Tag.objects.filter( post_competition=False), to_attr='tags'), - 'first_blood', 'hint_set__uses') + 'hint_set__uses').select_related('first_blood') if self.request.user.is_staff: categories = Category.objects else: @@ -101,12 +100,24 @@ def get_queryset(self): def list(self, request, *args, **kwargs): cache = caches['default'] categories = cache.get(get_cache_key(request.user)) + cache_hit = categories is not None if categories is None: queryset = self.filter_queryset(self.get_queryset()) - category_data = list(queryset) - serializer = self.get_serializer(category_data, many=True) + serializer = self.get_serializer(queryset, many=True) categories = serializer.data - cache.set(get_cache_key(request.user), categories, 30) + cache.set(get_cache_key(request.user), categories, 3600) + + if cache_hit: + solve_counts = get_solve_counts() + positive_votes = get_positive_votes() + negative_votes = get_negative_votes() + for category in categories: + for challenge in category['challenges']: + challenge['votes'] = { + 'positive': positive_votes.get(challenge['id'], 0), + 'negative': negative_votes.get(challenge['id'], 0) + } + challenge['solve_count'] = solve_counts.get(challenge['id'], 0) # This is to fix an issue with django duplicating challenges on .annotate. # If you want to clean this up, good luck. @@ -271,6 +282,7 @@ def post(self, request): user.save() team.save() flag_score.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag, solve=solve) + caches['default'].delete(get_cache_key(request.user)) ret = {'correct': True} if challenge.post_score_explanation: ret["explanation"] = challenge.post_score_explanation diff --git a/src/hint/views.py b/src/hint/views.py index 2ddf6ced..180d47de 100644 --- a/src/hint/views.py +++ b/src/hint/views.py @@ -1,3 +1,4 @@ +from django.core.cache import caches from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.status import HTTP_403_FORBIDDEN @@ -7,7 +8,7 @@ from backend.response import FormattedResponse from backend.viewsets import AdminCreateModelViewSet from challenge.permissions import CompetitionOpen -from config import config +from challenge.views import get_cache_key from hint.models import Hint, HintUse from hint.permissions import HasUsedHint from hint.serializers import ( @@ -57,4 +58,5 @@ def post(self, request): challenge=hint.challenge, ).save() serializer = FullHintSerializer(hint, context={"request": request}) + caches['default'].delete(get_cache_key(request.user)) return FormattedResponse(d=serializer.data) From 8d3a80e1b45951f9782325000cd5f1158d0e93ff Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:22:56 +0100 Subject: [PATCH 018/185] simplify queryset --- src/challenge/views.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/challenge/views.py b/src/challenge/views.py index 3fca27f0..c4a31bf4 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -57,26 +57,13 @@ def get_queryset(self): if self.request.user.is_staff and self.request.user.should_deny_admin(): return Category.objects.none() team = self.request.user.team - if team is not None: - challenges = Challenge.objects.annotate( - unlock_time_surpassed=Case( - When(release_time__lte=timezone.now(), then=Value(True)), - default=Value(False), - output_field=models.BooleanField(), - ) - ) - else: - challenges = ( - Challenge.objects.annotate( - solved=Value(False, models.BooleanField()), - unlock_time_surpassed=Case( - When(release_time__lte=timezone.now(), then=Value(True)), - default=Value(False), - output_field=models.BooleanField(), - ) - ) + challenges = Challenge.objects.annotate( + unlock_time_surpassed=Case( + When(release_time__lte=timezone.now(), then=Value(True)), + default=Value(False), + output_field=models.BooleanField(), ) - x = challenges.prefetch_related( + ).prefetch_related( Prefetch('hint_set', queryset=Hint.objects.annotate( used=Case( When(id__in=HintUse.objects.filter(team=team).values_list('hint_id'), then=Value(True)), @@ -93,7 +80,7 @@ def get_queryset(self): else: categories = Category.objects.filter(release_time__lte=timezone.now()) qs = categories.prefetch_related( - Prefetch('category_challenges', queryset=x, to_attr='challenges') + Prefetch('category_challenges', queryset=challenges, to_attr='challenges') ) return qs From a8051ed0a175297601af036aa49aecbc79db7a21 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:26:32 +0100 Subject: [PATCH 019/185] disable rate limits for testing --- src/backend/settings/test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/settings/test.py b/src/backend/settings/test.py index e0b4f340..0322c362 100644 --- a/src/backend/settings/test.py +++ b/src/backend/settings/test.py @@ -8,6 +8,9 @@ FRONTEND_URL = "http://example.com/" DOMAIN = "example.com" +for scope in REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]: + REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"][scope] = "9999999/minute" + #DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', From 11e10a3ba7adf364f1f3c914a842c149ea671076 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:32:42 +0100 Subject: [PATCH 020/185] cache leaderboard graph --- src/leaderboard/views.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index 41a7cd43..056a066a 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -1,6 +1,7 @@ import time from django.contrib.auth import get_user_model +from django.core.cache import caches from rest_framework.generics import ListAPIView from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer from rest_framework.response import Response @@ -38,6 +39,11 @@ def get(self, request, *args, **kwargs): if should_hide_scoreboard(): return FormattedResponse({}) + cache = caches['default'] + cached_leaderboard = cache.get('leaderboard_graph') + if cached_leaderboard is not None: + return FormattedResponse(cached_leaderboard) + graph_members = config.get('graph_members') top_teams = Team.objects.visible().ranked()[:graph_members] top_users = get_user_model().objects.filter(is_visible=True).order_by('-leaderboard_points', 'last_score')[ @@ -50,9 +56,12 @@ def get(self, request, *args, **kwargs): user_serializer = LeaderboardUserScoreSerializer(user_scores, many=True) team_serializer = LeaderboardTeamScoreSerializer(team_scores, many=True) + response = {'user': user_serializer.data} if config.get('enable_teams'): - return FormattedResponse({'team': team_serializer.data, 'user': user_serializer.data}) - return FormattedResponse({'user': user_serializer.data}) + response['team'] = team_serializer.data + + cache.set('leaderboard_graph', response, 15) + return FormattedResponse(response) class UserListView(ListAPIView): From ee0f4a9f306e6282cf469693b9f619d541aad57e Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:51:25 +0100 Subject: [PATCH 021/185] ~100x speedup on stats full --- src/challenge/serializers.py | 15 +++++++++++++-- src/stats/views.py | 12 ++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 13af0fac..bbe054f4 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -15,13 +15,24 @@ def get_solve_counts(): if solve_counts is not None: return solve_counts with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') solve_counts = {i[0]: i[1] for i in cursor.fetchall()} cache.set('solve_counts', solve_counts, 15) return solve_counts +def get_incorrect_solve_counts(): + cache = caches['default'] + solve_counts = cache.get('incorrect_solve_counts') + if solve_counts is not None: + return solve_counts + with connection.cursor() as cursor: + cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('incorrect_solve_counts', solve_counts, 15) + return solve_counts + + def get_positive_votes(): cache = caches['default'] positive_votes = cache.get('positive_votes') diff --git a/src/stats/views.py b/src/stats/views.py index 934ae550..b1dec76f 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -8,6 +8,7 @@ from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView +from challenge.serializers import get_solve_counts, get_incorrect_solve_counts from member.models import UserIP from backend.response import FormattedResponse from challenge.models import Solve, Score, Challenge @@ -46,10 +47,13 @@ def stats(request): @permission_classes([IsAdminUser]) def full(request): challenge_data = {} - for challenge in Challenge.objects.prefetch_related('solves').all(): - challenge_data[challenge.id] = {} - challenge_data[challenge.id]["correct"] = challenge.solves.filter(correct=True).count() - challenge_data[challenge.id]["incorrect"] = challenge.solves.filter(correct=False).count() + correct_solve_counts = get_solve_counts() + incorrect_solve_counts = get_incorrect_solve_counts() + for challenge in correct_solve_counts: + challenge_data[challenge] = {} + challenge_data[challenge]["correct"] = correct_solve_counts[challenge] + for challenge in incorrect_solve_counts: + challenge_data[challenge]["incorrect"] = incorrect_solve_counts[challenge] point_distribution = {} for team in Team.objects.all(): From a9f81d561e40b69367c141452ac9352ccfd3edfa Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 05:56:40 +0100 Subject: [PATCH 022/185] significant stats speedup --- src/stats/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/stats/views.py b/src/stats/views.py index b1dec76f..867dc3cb 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -34,11 +34,15 @@ def stats(request): average = users / teams else: average = 0 + + solve_count = sum(get_solve_counts().values()) + total_solve_count = solve_count + sum(get_incorrect_solve_counts().values()) + return FormattedResponse({ "user_count": users, "team_count": teams, - "solve_count": Solve.objects.count(), - "correct_solve_count": Solve.objects.filter(correct=True).count(), + "solve_count": total_solve_count, + "correct_solve_count": solve_count, "avg_members": average, }) From 33e1e78c7196465796d97eb454ad1fd587b64a24 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 06:02:55 +0100 Subject: [PATCH 023/185] move raw sql out of serializers.py --- src/challenge/serializers.py | 57 ++---------------------------------- src/challenge/sql.py | 52 ++++++++++++++++++++++++++++++++ src/stats/views.py | 6 ++-- 3 files changed, 57 insertions(+), 58 deletions(-) create mode 100644 src/challenge/sql.py diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index bbe054f4..c6808deb 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -6,59 +6,10 @@ from rest_framework import serializers from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote +from challenge.sql import get_solve_counts, get_positive_votes, get_negative_votes from hint.serializers import HintSerializer, FastHintSerializer -def get_solve_counts(): - cache = caches['default'] - solve_counts = cache.get('solve_counts') - if solve_counts is not None: - return solve_counts - with connection.cursor() as cursor: - cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('solve_counts', solve_counts, 15) - return solve_counts - - -def get_incorrect_solve_counts(): - cache = caches['default'] - solve_counts = cache.get('incorrect_solve_counts') - if solve_counts is not None: - return solve_counts - with connection.cursor() as cursor: - cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('incorrect_solve_counts', solve_counts, 15) - return solve_counts - - -def get_positive_votes(): - cache = caches['default'] - positive_votes = cache.get('positive_votes') - if positive_votes is not None: - return positive_votes - with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;') - positive_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('positive_votes', cache.get('positive_votes'), 15) - return positive_votes - - -def get_negative_votes(): - cache = caches['default'] - negative_votes = cache.get('negative_votes') - if negative_votes is not None: - return negative_votes - with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;') - negative_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('negative_votes', cache.get('negative_votes'), 15) - return negative_votes - - class ForeignKeyField(serpy.Field): """A :class:`Field` that gets a given attribute from a foreign object.""" def __init__(self, *args, attr_name="id", **kwargs): @@ -222,16 +173,12 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') - solve_counts = {i[0]: i[1] for i in cursor.fetchall()} self.fields['challenges'].context.update({ "request": self.context["request"], "solves": list( self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) ), - "solve_counter": solve_counts, + "solve_counter": get_solve_counts(), "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), }) diff --git a/src/challenge/sql.py b/src/challenge/sql.py new file mode 100644 index 00000000..bfc4a223 --- /dev/null +++ b/src/challenge/sql.py @@ -0,0 +1,52 @@ +from django.core.cache import caches +from django.db import connection + + +def get_solve_counts(): + cache = caches['default'] + solve_counts = cache.get('solve_counts') + if solve_counts is not None: + return solve_counts + with connection.cursor() as cursor: + cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('solve_counts', solve_counts, 15) + return solve_counts + + +def get_incorrect_solve_counts(): + cache = caches['default'] + solve_counts = cache.get('incorrect_solve_counts') + if solve_counts is not None: + return solve_counts + with connection.cursor() as cursor: + cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') + solve_counts = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('incorrect_solve_counts', solve_counts, 15) + return solve_counts + + +def get_positive_votes(): + cache = caches['default'] + positive_votes = cache.get('positive_votes') + if positive_votes is not None: + return positive_votes + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;') + positive_votes = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('positive_votes', cache.get('positive_votes'), 15) + return positive_votes + + +def get_negative_votes(): + cache = caches['default'] + negative_votes = cache.get('negative_votes') + if negative_votes is not None: + return negative_votes + with connection.cursor() as cursor: + cursor.execute( + 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;') + negative_votes = {i[0]: i[1] for i in cursor.fetchall()} + cache.set('negative_votes', cache.get('negative_votes'), 15) + return negative_votes \ No newline at end of file diff --git a/src/stats/views.py b/src/stats/views.py index 867dc3cb..6f6f885b 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -8,11 +8,11 @@ from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView -from challenge.serializers import get_solve_counts, get_incorrect_solve_counts -from member.models import UserIP from backend.response import FormattedResponse -from challenge.models import Solve, Score, Challenge +from challenge.models import Score +from challenge.sql import get_incorrect_solve_counts, get_solve_counts from config import config +from member.models import UserIP from team.models import Team From b5c6c1d5298956606b06c9afb430a1da62147111 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 06:08:31 +0100 Subject: [PATCH 024/185] make more tests pass --- src/challenge/models.py | 4 ++-- src/challenge/tests.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/challenge/models.py b/src/challenge/models.py index 0f303e2d..f1b44c3b 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -59,12 +59,12 @@ class Challenge(ExportModelOperationsMixin("challenge"), models.Model): release_time = models.DateTimeField(default=timezone.now) def is_unlocked(self, user, solves=None): - if not self.unlock_requirements: - return True if user is None: return False if not user.is_authenticated: return False + if not self.unlock_requirements: + return True if user.team is None: return False if solves is None: diff --git a/src/challenge/tests.py b/src/challenge/tests.py index fad2bf28..da3a605a 100644 --- a/src/challenge/tests.py +++ b/src/challenge/tests.py @@ -21,10 +21,10 @@ def setUp(self): author='dave', score=1000) challenge2 = Challenge(name='test2', category=category, description='a', challenge_type='basic', challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000, auto_unlock=True) + author='dave', score=1000) challenge3 = Challenge(name='test3', category=category, description='a', challenge_type='basic', challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000, auto_unlock=False) + author='dave', score=1000) challenge1.save() challenge2.save() challenge3.save() @@ -346,7 +346,7 @@ def test_create_challenge(self): response = self.client.post(reverse('challenges-list'), data={ 'name': 'test4', 'category': self.category.id, 'description': 'abc', 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'auto_unlock': True, 'score': 1000, 'unlock_requirements': "", 'flag_metadata': {}, + 'author': 'dave', 'score': 1000, 'unlock_requirements': "", 'flag_metadata': {}, 'tags': [], }, format='json') self.assertEquals(response.status_code, HTTP_201_CREATED) @@ -358,7 +358,7 @@ def test_create_challenge_unauthorized(self): response = self.client.post(reverse('challenges-list'), data={ 'name': 'test4', 'category': self.category.id, 'description': 'abc', 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'auto_unlock': True, 'score': 1000, 'unlock_requirements': "a", 'flag_metadata': {} + 'author': 'dave', 'score': 1000, 'unlock_requirements': "a", 'flag_metadata': {} }, format='json') self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) From afdbfda971a9a6356cdf9389b1b1b780f1179664 Mon Sep 17 00:00:00 2001 From: David Cooke Date: Mon, 12 Apr 2021 06:15:25 +0100 Subject: [PATCH 025/185] add config value to disable caching --- src/challenge/sql.py | 10 ++++++---- src/challenge/views.py | 2 +- src/config/config.py | 1 + src/leaderboard/views.py | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/challenge/sql.py b/src/challenge/sql.py index bfc4a223..91612572 100644 --- a/src/challenge/sql.py +++ b/src/challenge/sql.py @@ -1,11 +1,13 @@ from django.core.cache import caches from django.db import connection +from config import config + def get_solve_counts(): cache = caches['default'] solve_counts = cache.get('solve_counts') - if solve_counts is not None: + if solve_counts is not None and config.get('enable_caching'): return solve_counts with connection.cursor() as cursor: cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') @@ -17,7 +19,7 @@ def get_solve_counts(): def get_incorrect_solve_counts(): cache = caches['default'] solve_counts = cache.get('incorrect_solve_counts') - if solve_counts is not None: + if solve_counts is not None and config.get('enable_caching'): return solve_counts with connection.cursor() as cursor: cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') @@ -29,7 +31,7 @@ def get_incorrect_solve_counts(): def get_positive_votes(): cache = caches['default'] positive_votes = cache.get('positive_votes') - if positive_votes is not None: + if positive_votes is not None and config.get('enable_caching'): return positive_votes with connection.cursor() as cursor: cursor.execute( @@ -42,7 +44,7 @@ def get_positive_votes(): def get_negative_votes(): cache = caches['default'] negative_votes = cache.get('negative_votes') - if negative_votes is not None: + if negative_votes is not None and config.get('enable_caching'): return negative_votes with connection.cursor() as cursor: cursor.execute( diff --git a/src/challenge/views.py b/src/challenge/views.py index c4a31bf4..93625dd9 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -88,7 +88,7 @@ def list(self, request, *args, **kwargs): cache = caches['default'] categories = cache.get(get_cache_key(request.user)) cache_hit = categories is not None - if categories is None: + if categories is None or not config.get('enable_caching'): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) categories = serializer.data diff --git a/src/config/config.py b/src/config/config.py index c691f272..c02c3480 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -17,6 +17,7 @@ 'registration_provider': 'basic_auth', 'token_provider': 'basic_auth', 'enable_bot_users': True, + 'enable_caching': True, 'enable_ctftime': True, 'enable_flag_submission': True, 'enable_flag_submission_after_competition': True, diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index 056a066a..d91d787e 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -41,7 +41,7 @@ def get(self, request, *args, **kwargs): cache = caches['default'] cached_leaderboard = cache.get('leaderboard_graph') - if cached_leaderboard is not None: + if cached_leaderboard is not None and config.get('enable_caching'): return FormattedResponse(cached_leaderboard) graph_members = config.get('graph_members') From e187687af2f222430f10df429829776a3ad67ab9 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 13 Apr 2021 21:31:33 +0100 Subject: [PATCH 026/185] Reformat config.py imports so they no longer break everything It is not truth that matters, but victory --- src/authentication/providers.py | 4 ++-- src/authentication/serializers.py | 12 +++++----- src/authentication/tests.py | 2 +- src/backend/authentication.py | 2 +- src/backend/permissions.py | 2 +- src/challenge/models.py | 4 ++-- src/challenge/permissions.py | 2 +- src/challenge/tests.py | 2 +- src/challenge/views.py | 2 +- src/config/tests.py | 4 ++-- src/config/views.py | 18 +++++++-------- src/leaderboard/tests.py | 2 +- src/leaderboard/views.py | 2 +- src/member/models.py | 8 +++---- src/member/serializers.py | 2 +- src/plugins/flag/lenient.py | 2 +- src/plugins/points/base.py | 6 ++--- src/plugins/providers.py | 2 +- src/plugins/tests.py | 2 +- src/ractf/management/commands/copy_points.py | 2 +- src/scorerecalculator/views.py | 2 +- src/stats/views.py | 8 +++---- src/team/permissions.py | 2 +- src/team/tests.py | 24 ++++++++++---------- src/team/views.py | 8 +++---- src/websockets/signals.py | 2 +- 26 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/authentication/providers.py b/src/authentication/providers.py index 748c795e..c8950e1f 100644 --- a/src/authentication/providers.py +++ b/src/authentication/providers.py @@ -4,7 +4,7 @@ from django.core.validators import EmailValidator from rest_framework.exceptions import ValidationError -from config import config +import config from plugins.providers import Provider @@ -20,7 +20,7 @@ def register_user(self, **kwargs): pass def validate_email(self, email): - allow_domain = config.get('email_allow') + allow_domain = config.config.get('email_allow') if allow_domain: email_validator = EmailValidator(allow_domain) else: diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 2cfeec7b..7860d2ba 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -8,12 +8,12 @@ from rest_framework.generics import get_object_or_404 from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST +import config from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException from backend.mail import send_email from backend.response import FormattedResponse from backend.signals import register -from config import config from plugins import providers from team.models import Team @@ -41,13 +41,13 @@ def validate(self, data): class RegistrationSerializer(serializers.Serializer): def validate(self, _): - register_end_time = config.get('register_end_time') - if not (config.get('enable_registration') and time.time() >= config.get('register_start_time')) \ + register_end_time = config.config.get('register_end_time') + if not (config.config.get('enable_registration') and time.time() >= config.config.get('register_start_time')) \ and (register_end_time < 0 or register_end_time > time.time()): raise FormattedException(m='registration_not_open', status=HTTP_403_FORBIDDEN) validated_data = providers.get_provider('registration').validate(self.initial_data) - if config.get("invite_required"): + if config.config.get("invite_required"): if not self.initial_data.get("invite", None): raise FormattedException(m="invite_required", status=HTTP_400_BAD_REQUEST) validated_data["invite"] = self.initial_data["invite"] @@ -60,7 +60,7 @@ def create(self, validated_data): user.is_staff = True user.is_superuser = True - if config.get("invite_required"): + if config.config.get("invite_required"): if InviteCode.objects.filter(code=validated_data["invite"]): code = InviteCode.objects.get(code=validated_data["invite"]) if code: @@ -83,7 +83,7 @@ def create(self, validated_data): send_email(user.email, 'RACTF - Verify your email', 'verify', url=settings.FRONTEND_URL + 'verify?id={}&secret={}'.format(user.id, user.email_token)) - if not config.get("enable_teams"): + if not config.config.get("enable_teams"): user.save() user.team = Team.objects.create( owner=user, diff --git a/src/authentication/tests.py b/src/authentication/tests.py index e0e1f98c..a82d4726 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -8,11 +8,11 @@ HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED from rest_framework.test import APITestCase +import config from authentication.models import PasswordResetToken, TOTPDevice, InviteCode, BackupCode, Token from authentication.views import VerifyEmailView, DoPasswordResetView, AddTwoFactorView, VerifyTwoFactorView, LoginView, \ RegistrationView, ChangePasswordView, LoginTwoFactorView, RequestPasswordResetView, RegenerateBackupCodesView, \ CreateBotView -from config import config from team.models import Team diff --git a/src/backend/authentication.py b/src/backend/authentication.py index b4a789d7..6bc29c21 100644 --- a/src/backend/authentication.py +++ b/src/backend/authentication.py @@ -1,7 +1,7 @@ from rest_framework import authentication from authentication.models import Token -from config import config +import config class RactfTokenAuthentication(authentication.TokenAuthentication): diff --git a/src/backend/permissions.py b/src/backend/permissions.py index d270ab46..cecbd3b6 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -2,7 +2,7 @@ from rest_framework import permissions -from config import config +import config class AdminOrReadOnlyVisible(permissions.BasePermission): diff --git a/src/challenge/models.py b/src/challenge/models.py index f1b44c3b..d0e693b9 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -23,7 +23,7 @@ from django_prometheus.models import ExportModelOperationsMixin -from config import config +import config class Category(ExportModelOperationsMixin("category"), models.Model): @@ -149,7 +149,7 @@ def get_unlocked_annotated_queryset(cls, user): Prefetch( "tag_set", queryset=Tag.objects.all() - if time.time() > config.get("end_time") + if time.time() > config.config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags", ), diff --git a/src/challenge/permissions.py b/src/challenge/permissions.py index 5bc02c97..76e11873 100644 --- a/src/challenge/permissions.py +++ b/src/challenge/permissions.py @@ -2,7 +2,7 @@ from rest_framework import permissions -from config import config +import config class CompetitionOpen(permissions.BasePermission): diff --git a/src/challenge/tests.py b/src/challenge/tests.py index da3a605a..df04fd75 100644 --- a/src/challenge/tests.py +++ b/src/challenge/tests.py @@ -6,7 +6,7 @@ from rest_framework.test import APITestCase from challenge.models import Category, Challenge, Solve -from config import config +import config from hint.models import Hint, HintUse from team.models import Team diff --git a/src/challenge/views.py b/src/challenge/views.py index 93625dd9..d5931e26 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -30,7 +30,7 @@ CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, AdminScoreSerializer, FastCategorySerializer, get_solve_counts, get_positive_votes, get_negative_votes ) -from config import config +import config from hint.models import Hint, HintUse from plugins import plugins from team.models import Team diff --git a/src/config/tests.py b/src/config/tests.py index be48e509..bf1cfd40 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -3,7 +3,7 @@ from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT from rest_framework.test import APITestCase -from config import config +import config class ConfigTestCase(APITestCase): @@ -49,4 +49,4 @@ def test_update(self): response = self.client.patch(reverse('config-pk', kwargs={'name': 'test'}), data={'value': 'test2'}, format='json') self.assertEquals(response.status_code, HTTP_204_NO_CONTENT) - self.assertEquals(config.get('test'), 'test2') + self.assertEquals(config.config.get('test'), 'test2') diff --git a/src/config/views.py b/src/config/views.py index 628906f6..0ceacafb 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -1,8 +1,8 @@ from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_201_CREATED, HTTP_204_NO_CONTENT from rest_framework.views import APIView +import config from backend.response import FormattedResponse -from config import config from backend.permissions import AdminOrAnonymousReadOnly @@ -13,23 +13,23 @@ class ConfigView(APIView): def get(self, request, name=None): if name is None: if request.user.is_superuser: - return FormattedResponse(config.get_all()) - return FormattedResponse(config.get_all_non_sensitive()) - if not config.is_sensitive(name) or request.is_superuser: - return FormattedResponse(config.get(name)) + return FormattedResponse(config.config.get_all()) + return FormattedResponse(config.config.get_all_non_sensitive()) + if not config.config.is_sensitive(name) or request.is_superuser: + return FormattedResponse(config.config.get(name)) return FormattedResponse(status=HTTP_403_FORBIDDEN) def post(self, request, name): if "value" not in request.data: return FormattedResponse(status=HTTP_400_BAD_REQUEST) - config.set(name, request.data.get("value")) + config.config.set(name, request.data.get("value")) return FormattedResponse(status=HTTP_201_CREATED) def patch(self, request, name): if "value" not in request.data: return FormattedResponse(status=HTTP_400_BAD_REQUEST) - if config.get(name) is not None and isinstance(config.get(name), list): - config.set("name", config.get(name).append(request.data["value"])) + if config.config.get(name) is not None and isinstance(config.config.get(name), list): + config.config.set("name", config.config.get(name).append(request.data["value"])) return FormattedResponse() - config.set(name, request.data.get("value")) + config.config.set(name, request.data.get("value")) return FormattedResponse(status=HTTP_204_NO_CONTENT) diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 3d9b83d4..80b50d23 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -4,7 +4,7 @@ from rest_framework.test import APITestCase from challenge.models import Score, Solve, Category, Challenge -from config import config +import config from leaderboard.views import UserListView, TeamListView, GraphView, CTFTimeListView from team.models import Team diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index d91d787e..c5abbe6b 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -10,7 +10,7 @@ from backend.response import FormattedResponse from challenge.models import Score -from config import config +import config from leaderboard.serializers import LeaderboardUserScoreSerializer, LeaderboardTeamScoreSerializer, \ UserPointsSerializer, TeamPointsSerializer, CTFTimeSerializer, MatrixSerializer from team.models import Team diff --git a/src/member/models.py b/src/member/models.py index 0a4bfec3..10c703dd 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -12,8 +12,8 @@ from django_prometheus.models import ExportModelOperationsMixin +import config from backend.validators import printable_name -from config import config class TOTPStatus(IntEnum): @@ -56,8 +56,8 @@ def __str__(self): def can_login(self): return self.is_staff or ( - config.get("enable_login") - and (config.get("enable_prelogin") or config.get("start_time") <= time.time()) + config.config.get("enable_login") + and (config.config.get("enable_prelogin") or config.config.get("start_time") <= time.time()) ) def issue_token(self, owner=None): @@ -71,7 +71,7 @@ def has_2fa(self): return hasattr(self, "totp_device") and self.totp_device.verified def should_deny_admin(self): - return config.get("enable_force_admin_2fa") and not self.has_2fa() + return config.config.get("enable_force_admin_2fa") and not self.has_2fa() class UserIP(ExportModelOperationsMixin("user_ip"), models.Model): diff --git a/src/member/serializers.py b/src/member/serializers.py index a26d48ef..ca1dc157 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -6,7 +6,7 @@ from backend.mixins import IncorrectSolvesMixin from challenge.serializers import SolveSerializer from member.models import UserIP -from config import config +import config class MemberSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): diff --git a/src/plugins/flag/lenient.py b/src/plugins/flag/lenient.py index 5b37a6bc..5d9c7c34 100644 --- a/src/plugins/flag/lenient.py +++ b/src/plugins/flag/lenient.py @@ -1,4 +1,4 @@ -from config import config +import config from plugins.flag.base import FlagPlugin import unicodedata diff --git a/src/plugins/points/base.py b/src/plugins/points/base.py index 24f0b19f..ebd4f8f1 100644 --- a/src/plugins/points/base.py +++ b/src/plugins/points/base.py @@ -4,8 +4,8 @@ from django.db.models import Sum, F from django.utils import timezone +import config from challenge.models import Score, Solve -from config import config from hint.models import HintUse @@ -32,7 +32,7 @@ def score(self, user, team, flag, solves, *args, **kwargs): deducted = HintUse.objects.filter(user=user, challenge=challenge).aggregate(points=Sum(F('hint__penalty'))) deducted = 0 if deducted['points'] is None else deducted['points'] deducted = min(points, deducted) - scored = config.get('end_time') >= time.time() and config.get('enable_scoring') + scored = config.config.get('end_time') >= time.time() and config.config.get('enable_scoring') score = Score(team=team, reason='challenge', points=points, penalty=deducted, leaderboard=scored, user=user) score.save() solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, @@ -50,5 +50,5 @@ def score(self, user, team, flag, solves, *args, **kwargs): return solve def register_incorrect_attempt(self, user, team, flag, solves, *args, **kwargs): - if config.get('enable_track_incorrect_submissions'): + if config.config.get('enable_track_incorrect_submissions'): Solve(team=team, solved_by=user, challenge=self.challenge, flag=flag, correct=False, score=None).save() diff --git a/src/plugins/providers.py b/src/plugins/providers.py index b55b2da1..95b70d1a 100644 --- a/src/plugins/providers.py +++ b/src/plugins/providers.py @@ -1,7 +1,7 @@ import abc from collections import defaultdict -from config import config +import config providers = defaultdict(dict) diff --git a/src/plugins/tests.py b/src/plugins/tests.py index a50ba301..76991c14 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -3,7 +3,7 @@ from challenge.models import Category, Challenge, Solve, Score from challenge.tests import ChallengeSetupMixin -from config import config +import config from plugins import plugins from plugins.flag.hashed import HashedFlagPlugin from plugins.flag.lenient import LenientFlagPlugin diff --git a/src/ractf/management/commands/copy_points.py b/src/ractf/management/commands/copy_points.py index 662ae700..4230302a 100644 --- a/src/ractf/management/commands/copy_points.py +++ b/src/ractf/management/commands/copy_points.py @@ -3,7 +3,7 @@ from django.core.management import BaseCommand from challenge.models import Score -from config import config +import config class Command(BaseCommand): diff --git a/src/scorerecalculator/views.py b/src/scorerecalculator/views.py index 9d5ccc58..76a71355 100644 --- a/src/scorerecalculator/views.py +++ b/src/scorerecalculator/views.py @@ -6,7 +6,7 @@ from backend.response import FormattedResponse from challenge.models import Score -from config import config +import config from team.models import Team diff --git a/src/stats/views.py b/src/stats/views.py index 6f6f885b..876f6ae9 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -8,10 +8,10 @@ from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView +import config from backend.response import FormattedResponse from challenge.models import Score from challenge.sql import get_incorrect_solve_counts, get_solve_counts -from config import config from member.models import UserIP from team.models import Team @@ -19,9 +19,9 @@ @api_view(['GET']) def countdown(request): return FormattedResponse({ - "countdown_timestamp": config.get('start_time'), - "registration_open": config.get('register_start_time'), - "competition_end": config.get('end_time'), + "countdown_timestamp": config.config.get('start_time'), + "registration_open": config.config.get('register_start_time'), + "competition_end": config.config.get('end_time'), "server_timestamp": datetime.now(timezone.utc).isoformat(), }) diff --git a/src/team/permissions.py b/src/team/permissions.py index 3d88eec9..de7cd98f 100644 --- a/src/team/permissions.py +++ b/src/team/permissions.py @@ -1,6 +1,6 @@ from rest_framework import permissions -from config import config +import config class IsTeamOwnerOrReadOnly(permissions.BasePermission): diff --git a/src/team/tests.py b/src/team/tests.py index 44e5b9af..318723f1 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -10,8 +10,8 @@ ) from rest_framework.test import APITestCase +import config from challenge.models import Solve, Category, Challenge -from config import config from team.models import Team @@ -72,13 +72,13 @@ def test_update_not_owner(self): def test_team_leave_disabled(self): self.client.force_authenticate(user=self.user) - config.set("enable_team_leave", False) + config.config.set("enable_team_leave", False) response = self.client.post(reverse("team-leave")) - config.set("enable_team_leave", True) + config.config.set("enable_team_leave", True) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_challenge_solved(self): - config.set("enable_team_leave", True) + config.config.set("enable_team_leave", True) self.client.force_authenticate(user=self.user) category = Category.objects.create(name="test category", display_order=1, contained_type="test", description="test") @@ -96,7 +96,7 @@ def test_team_leave_challenge_solved(self): Solve.objects.create(solved_by=self.user, flag="", challenge=chall) response = self.client.post(reverse("team-leave")) - config.set("enable_team_leave", False) + config.config.set("enable_team_leave", False) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_with_members(self): @@ -106,17 +106,17 @@ def test_team_leave_as_owner_with_members(self): self.admin_user.is_staff = False self.admin_user.save() - config.set("enable_team_leave", True) + config.config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) - config.set("enable_team_leave", False) + config.config.set("enable_team_leave", False) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_without_members(self): self.client.force_authenticate(user=self.user) - config.set("enable_team_leave", True) + config.config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) - config.set("enable_team_leave", False) + config.config.set("enable_team_leave", False) self.assertEquals(response.status_code, HTTP_200_OK) @@ -177,7 +177,7 @@ def test_join_team_full(self): ) user2.save() self.client.force_authenticate(self.admin_user) - config.set("team_size", 1) + config.config.set("team_size", 1) self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) @@ -189,11 +189,11 @@ def test_join_team_full(self): def test_join_team_disabled(self): self.client.force_authenticate(self.admin_user) - config.set("enable_team_join", False) + config.config.set("enable_team_join", False) response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - config.set("enable_team_join", True) + config.config.set("enable_team_join", True) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_duplicate(self): diff --git a/src/team/views.py b/src/team/views.py index c20a9a35..3d7403b6 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -10,12 +10,12 @@ from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND from rest_framework.views import APIView +import config from backend.exceptions import FormattedException from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.response import FormattedResponse from backend.signals import team_join_attempt, team_join_reject, team_join from backend.viewsets import AdminListModelViewSet -from config import config from team.models import Team from challenge.models import Solve from member.models import Member @@ -101,7 +101,7 @@ class JoinTeamView(APIView): throttle_scope = "team_join" def post(self, request): - if not config.get("enable_team_join"): + if not config.config.get("enable_team_join"): return FormattedResponse(m="join_disabled", status=HTTP_403_FORBIDDEN) name = request.data.get("name") password = request.data.get("password") @@ -115,7 +115,7 @@ def post(self, request): except Http404: team_join_reject.send(sender=self.__class__, user=request.user, name=name) raise FormattedException(m='invalid_team', status=HTTP_404_NOT_FOUND) - team_size = int(config.get('team_size')) + team_size = int(config.config.get('team_size')) if not request.user.is_staff and not team.size_limit_exempt and 0 < team_size <= team.members.count(): return FormattedResponse(m='team_full', status=HTTP_403_FORBIDDEN) request.user.team = team @@ -133,7 +133,7 @@ class LeaveTeamView(APIView): permission_classes = (IsAuthenticated & HasTeam & TeamsEnabled,) def post(self, request): - if not config.get('enable_team_leave'): + if not config.config.get('enable_team_leave'): return FormattedResponse(m='leave_disabled', status=HTTP_403_FORBIDDEN) if Solve.objects.filter(solved_by=request.user).exists(): return FormattedResponse(m='challenge_solved', status=HTTP_403_FORBIDDEN) diff --git a/src/websockets/signals.py b/src/websockets/signals.py index a7a26661..e28e9429 100644 --- a/src/websockets/signals.py +++ b/src/websockets/signals.py @@ -6,7 +6,7 @@ from announcements.models import Announcement from announcements.serializers import AnnouncementSerializer from backend.signals import flag_score, flag_reject, use_hint, team_join -from config import config +import config def get_team_channel(user): From 9b9d60e549935b58dab320c1c9a27a666af04b5b Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 13 Apr 2021 21:37:23 +0100 Subject: [PATCH 027/185] Add autoflake to dev dependencies --- poetry.lock | 741 +++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 2 files changed, 382 insertions(+), 360 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5efcc220..c07995f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,72 +1,71 @@ [[package]] -category = "main" -description = "asyncio (PEP 3156) Redis support" name = "aioredis" +version = "1.3.1" +description = "asyncio (PEP 3156) Redis support" +category = "main" optional = false python-versions = "*" -version = "1.3.1" [package.dependencies] async-timeout = "*" hiredis = "*" [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Disable App Nap on macOS >= 10.9" -marker = "sys_platform == \"darwin\"" name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" optional = false python-versions = "*" -version = "0.1.2" [[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" name = "asgiref" +version = "3.3.1" +description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.5" -version = "3.3.1" [package.extras] tests = ["pytest", "pytest-asyncio"] [[package]] -category = "main" -description = "Timeout context manager for asyncio programs" name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.5.3" -version = "3.0.1" [[package]] -category = "main" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "main" -description = "WebSocket client & server library, WAMP real-time framework" name = "autobahn" +version = "20.12.3" +description = "WebSocket client & server library, WAMP real-time framework" +category = "main" optional = false python-versions = ">=3.6" -version = "20.12.3" [package.dependencies] cryptography = ">=2.9.2" @@ -86,12 +85,23 @@ twisted = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)"] xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] [[package]] -category = "main" -description = "Self-service finite-state machines for the programmer on the go." -name = "automat" +name = "autoflake" +version = "1.4" +description = "Removes unused imports and unused variables" +category = "dev" optional = false python-versions = "*" + +[package.dependencies] +pyflakes = ">=1.1.0" + +[[package]] +name = "automat" version = "20.2.0" +description = "Self-service finite-state machines for the programmer on the go." +category = "main" +optional = false +python-versions = "*" [package.dependencies] attrs = ">=19.2.0" @@ -101,32 +111,32 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -category = "main" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" name = "autopep8" +version = "1.5.4" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "main" optional = false python-versions = "*" -version = "1.5.4" [package.dependencies] pycodestyle = ">=2.6.0" toml = "*" [[package]] -category = "dev" -description = "Specifications for callback functions passed in to an API" name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "20.8b1" [package.dependencies] appdirs = "*" @@ -143,12 +153,12 @@ colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "The AWS SDK for Python" name = "boto3" +version = "1.16.43" +description = "The AWS SDK for Python" +category = "main" optional = false python-versions = "*" -version = "1.16.43" [package.dependencies] botocore = ">=1.19.43,<1.20.0" @@ -156,28 +166,25 @@ jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" [[package]] -category = "main" -description = "Low-level, data-driven core of boto 3." name = "botocore" +version = "1.19.43" +description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = "*" -version = "1.19.43" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" python-dateutil = ">=2.1,<3.0.0" - -[package.dependencies.urllib3] -python = "<3.4.0 || >=3.5.0" -version = ">=1.25.4,<1.27" +urllib3 = {version = ">=1.25.4,<1.27", markers = "python_version != \"3.4\""} [[package]] -category = "main" -description = "Minimal persistent memoization cache" name = "cachalot" +version = "1.5.0" +description = "Minimal persistent memoization cache" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "1.5.0" [package.dependencies] jsonpickle = "*" @@ -185,47 +192,47 @@ tinydb = ">=4,<5" tinydb-smartcache = "*" [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.12.5" [[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." name = "cffi" +version = "1.14.4" +description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" -version = "1.14.4" [package.dependencies] pycparser = "*" [[package]] -category = "main" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." name = "channels" +version = "2.4.0" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] -Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" +Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -category = "main" -description = "Redis-backed ASGI channel layer implementation" name = "channels-redis" +version = "2.4.1" +description = "Redis-backed ASGI channel layer implementation" +category = "main" optional = false python-versions = ">=3.6" -version = "2.4.1" [package.dependencies] aioredis = ">=1.0,<2.0" @@ -238,45 +245,44 @@ cryptography = ["cryptography (>=1.3.0)"] tests = ["cryptography (>=1.3.0)", "pytest (>=3.6.0,<3.7.0)", "pytest-asyncio (>=0.8,<1.0)", "async-generator (>=1.8,<2.0)", "async-timeout (>=2.0,<3.0)"] [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.0.0" [[package]] -category = "dev" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" [[package]] -category = "main" -description = "Symbolic constants in Python" name = "constantly" +version = "15.1.0" +description = "Symbolic constants in Python" +category = "main" optional = false python-versions = "*" -version = "15.1.0" [[package]] -category = "dev" -description = "Python client library for Core API." name = "coreapi" +version = "2.3.3" +description = "Python client library for Core API." +category = "dev" optional = false python-versions = "*" -version = "2.3.3" [package.dependencies] coreschema = "*" @@ -285,80 +291,77 @@ requests = "*" uritemplate = "*" [[package]] -category = "dev" -description = "Core Schema." name = "coreschema" +version = "0.0.4" +description = "Core Schema." +category = "dev" optional = false python-versions = "*" -version = "0.0.4" [package.dependencies] jinja2 = "*" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3.1" +description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.3.1" [package.extras] toml = ["toml"] [[package]] -category = "main" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." name = "cryptography" +version = "3.3.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" -version = "3.3.1" [package.dependencies] cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -category = "main" -description = "Django ASGI (HTTP/WebSocket) server" name = "daphne" +version = "2.5.0" +description = "Django ASGI (HTTP/WebSocket) server" +category = "main" optional = false python-versions = "*" -version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" - -[package.dependencies.twisted] -extras = ["tls"] -version = ">=18.7" +twisted = {version = ">=18.7", extras = ["tls"]} [package.extras] -tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] [[package]] -category = "dev" -description = "Decorators for Humans" name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.4.2" [[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" +version = "3.1.4" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.6" -version = "3.1.4" [package.dependencies] asgiref = ">=3.2.10,<4" @@ -370,99 +373,99 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -category = "main" -description = "Caches your Django ORM queries and automatically invalidates them." name = "django-cachalot" +version = "2.3.5" +description = "Caches your Django ORM queries and automatically invalidates them." +category = "main" optional = false python-versions = "*" -version = "2.3.5" [package.dependencies] Django = ">=2" [[package]] -category = "main" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." name = "django-cors-headers" +version = "3.2.1" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +category = "main" optional = false python-versions = ">=3.5" -version = "3.2.1" [package.dependencies] Django = ">=1.11" [[package]] -category = "main" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." name = "django-filter" +version = "2.4.0" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "Django middlewares to monitor your application with Prometheus.io." name = "django-prometheus" +version = "2.1.0" +description = "Django middlewares to monitor your application with Prometheus.io." +category = "main" optional = false python-versions = "*" -version = "2.1.0" [package.dependencies] prometheus-client = ">=0.7" [[package]] -category = "main" -description = "Full featured redis cache backend for Django." name = "django-redis" +version = "4.11.0" +description = "Full featured redis cache backend for Django." +category = "main" optional = false python-versions = ">=3.5" -version = "4.11.0" [package.dependencies] Django = ">=1.11" redis = ">=2.10.0" [[package]] -category = "main" -description = "Redis Cache Backend for Django" name = "django-redis-cache" +version = "2.1.1" +description = "Redis Cache Backend for Django" +category = "main" optional = false python-versions = "*" -version = "2.1.1" [package.dependencies] redis = "<4.0" six = "*" [[package]] -category = "main" -description = "Silky smooth profiling for the Django Framework" name = "django-silk" +version = "4.1.0" +description = "Silky smooth profiling for the Django Framework" +category = "main" optional = false python-versions = ">=3.5" -version = "4.1.0" [package.dependencies] +autopep8 = "*" Django = ">=2.2" +gprof2dot = ">=2017.09.19" Jinja2 = "*" Pygments = "*" -autopep8 = "*" -gprof2dot = ">=2017.09.19" python-dateutil = "*" pytz = "*" requests = "*" sqlparse = "*" [[package]] -category = "main" -description = "Support for many storage backends in Django" name = "django-storages" +version = "1.9.1" +description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.1" [package.dependencies] Django = ">=1.11" @@ -476,12 +479,12 @@ libcloud = ["apache-libcloud"] sftp = ["paramiko"] [[package]] -category = "dev" -description = "Mypy stubs for Django" name = "django-stubs" +version = "1.7.0" +description = "Mypy stubs for Django" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.7.0" [package.dependencies] django = "*" @@ -489,35 +492,35 @@ mypy = ">=0.790" typing-extensions = "*" [[package]] -category = "main" -description = "A translatable password validator for django, based on zxcvbn-python." name = "django-zxcvbn-password-validator" +version = "1.3.0" +description = "A translatable password validator for django, based on zxcvbn-python." +category = "main" optional = false python-versions = "*" -version = "1.3.0" [package.dependencies] Django = ">=2.0" zxcvbn = "*" [[package]] -category = "main" -description = "Web APIs for Django, made easy." name = "djangorestframework" +version = "3.11.1" +description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.5" -version = "3.11.1" [package.dependencies] django = ">=1.11" [[package]] -category = "dev" -description = "PEP-484 stubs for django-rest-framework" name = "djangorestframework-stubs" +version = "1.3.0" +description = "PEP-484 stubs for django-rest-framework" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.3.0" [package.dependencies] coreapi = ">=2.0.0" @@ -527,23 +530,20 @@ requests = ">=2.0.0" typing-extensions = ">=3.7.2" [[package]] -category = "main" -description = "Generate a dot graph from the output of several profilers." name = "gprof2dot" +version = "2019.11.30" +description = "Generate a dot graph from the output of several profilers." +category = "main" optional = false python-versions = "*" -version = "2019.11.30" [[package]] -category = "main" -description = "WSGI HTTP Server for UNIX" name = "gunicorn" +version = "20.0.4" +description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.4" -version = "20.0.4" - -[package.dependencies] -setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.9.7)"] @@ -552,62 +552,61 @@ setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] [[package]] -category = "main" -description = "Python wrapper for hiredis" name = "hiredis" +version = "1.1.0" +description = "Python wrapper for hiredis" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" [[package]] -category = "main" -description = "A featureful, immutable, and correct URL for Python." name = "hyperlink" +version = "20.0.1" +description = "A featureful, immutable, and correct URL for Python." +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.0.1" [package.dependencies] idna = ">=2.5" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "main" -description = "" name = "incremental" +version = "17.5.0" +description = "" +category = "main" optional = false python-versions = "*" -version = "17.5.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] -category = "dev" -description = "IPython: Productive Interactive Computing" name = "ipython" +version = "7.19.0" +description = "IPython: Productive Interactive Computing" +category = "dev" optional = false python-versions = ">=3.7" -version = "7.19.0" [package.dependencies] -appnope = "*" +appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.10" -pexpect = ">4.3" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" -setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -622,43 +621,43 @@ qtconsole = ["qtconsole"] test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] [[package]] -category = "dev" -description = "Vestigial utilities from IPython" name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "Simple immutable types for python." name = "itypes" +version = "1.2.0" +description = "Simple immutable types for python." +category = "dev" optional = false python-versions = "*" -version = "1.2.0" [[package]] -category = "dev" -description = "An autocompletion tool for Python that can be used for text editors." name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" -version = "0.18.0" [package.dependencies] parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.11.2" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -667,49 +666,49 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "JSON Matching Expressions" name = "jmespath" +version = "0.10.0" +description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.0" [[package]] -category = "main" -description = "Python library for serializing any arbitrary object graph into JSON" name = "jsonpickle" +version = "2.0.0" +description = "Python library for serializing any arbitrary object graph into JSON" +category = "main" optional = false python-versions = ">=2.7" -version = "2.0.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] +testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] "testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] -category = "main" -description = "MessagePack (de)serializer." name = "msgpack" +version = "0.6.2" +description = "MessagePack (de)serializer." +category = "main" optional = false python-versions = "*" -version = "0.6.2" [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.790" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -720,153 +719,159 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "New Relic Python Agent" name = "newrelic" +version = "5.24.0.153" +description = "New Relic Python Agent" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "5.24.0.153" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] [[package]] -category = "dev" -description = "A Python Parser" name = "parso" +version = "0.8.1" +description = "A Python Parser" +category = "dev" optional = false python-versions = ">=3.6" -version = "0.8.1" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.1" [[package]] -category = "dev" -description = "Pexpect allows easy control of interactive console applications." -marker = "sys_platform != \"win32\"" name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" -version = "4.8.0" [package.dependencies] ptyprocess = ">=0.5" [[package]] -category = "dev" -description = "Tiny 'shelve'-like database with concurrency support" name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" optional = false python-versions = "*" -version = "0.7.5" [[package]] -category = "main" -description = "Python client for the Prometheus monitoring system." name = "prometheus-client" +version = "0.9.0" +description = "Python client for the Prometheus monitoring system." +category = "main" optional = false python-versions = "*" -version = "0.9.0" [package.extras] twisted = ["twisted"] [[package]] -category = "dev" -description = "Library for building powerful interactive command lines in Python" name = "prompt-toolkit" +version = "3.0.8" +description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.6.1" -version = "3.0.8" [package.dependencies] wcwidth = "*" [[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" name = "psycopg2-binary" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "2.8.6" [[package]] -category = "dev" -description = "Run a subprocess in a pseudo terminal" -marker = "sys_platform != \"win32\"" name = "ptyprocess" +version = "0.6.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" -version = "0.6.0" [[package]] -category = "main" -description = "ASN.1 types and codecs" name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" optional = false python-versions = "*" -version = "0.4.8" [[package]] -category = "main" -description = "A collection of ASN.1-based protocols modules." name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "main" optional = false python-versions = "*" -version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -category = "main" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "main" -description = "C parser in Python" name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" [[package]] -category = "main" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.3" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.5" -version = "2.7.3" [[package]] -category = "main" -description = "Python wrapper module around the OpenSSL library" name = "pyopenssl" +version = "20.0.1" +description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "20.0.1" [package.dependencies] cryptography = ">=3.2" @@ -877,74 +882,74 @@ docs = ["sphinx", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] -category = "main" -description = "Python One Time Password Library" name = "pyotp" +version = "2.3.0" +description = "Python One Time Password Library" +category = "main" optional = false python-versions = "*" -version = "2.3.0" [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "HTTP REST client, simplified for Python" name = "python-http-client" +version = "3.3.1" +description = "HTTP REST client, simplified for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.3.1" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.5" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2020.5" [[package]] -category = "dev" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "5.4.1" [[package]] -category = "main" -description = "Python client for Redis key-value store" name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.11.13" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.25.1" [package.dependencies] certifi = ">=2017.4.17" @@ -954,38 +959,38 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "main" -description = "An Amazon S3 Transfer Manager" name = "s3transfer" +version = "0.3.3" +description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = "*" -version = "0.3.3" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [[package]] -category = "main" -description = "Twilio SendGrid library for Python" name = "sendgrid" +version = "6.4.8" +description = "Twilio SendGrid library for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "6.4.8" [package.dependencies] python-http-client = ">=3.2.1" starkbank-ecdsa = ">=1.0.0" [[package]] -category = "main" -description = "Python client for Sentry (https://sentry.io)" name = "sentry-sdk" +version = "0.16.5" +description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" -version = "0.16.5" [package.dependencies] certifi = "*" @@ -1007,12 +1012,12 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] [[package]] -category = "main" -description = "Service identity verification for pyOpenSSL & cryptography." name = "service-identity" +version = "18.1.0" +description = "Service identity verification for pyOpenSSL & cryptography." +category = "main" optional = false python-versions = "*" -version = "18.1.0" [package.dependencies] attrs = ">=16.0.0" @@ -1027,63 +1032,63 @@ idna = ["idna"] tests = ["coverage (>=4.2.0)", "pytest"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "main" -description = "A non-validating SQL parser." name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" optional = false python-versions = ">=3.5" -version = "0.4.1" [[package]] -category = "main" -description = "A lightweight and fast pure python ECDSA library" name = "starkbank-ecdsa" +version = "1.1.0" +description = "A lightweight and fast pure python ECDSA library" +category = "main" optional = false python-versions = "*" -version = "1.1.0" [[package]] -category = "main" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" name = "tinydb" +version = "4.4.0" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "4.4.0" [[package]] -category = "main" -description = "A smarter query cache for TinyDB" name = "tinydb-smartcache" +version = "2.0.0" +description = "A smarter query cache for TinyDB" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "2.0.0" [package.dependencies] tinydb = ">=4.0,<5.0" [[package]] -category = "main" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.2" [[package]] -category = "dev" -description = "Traitlets Python configuration system" name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.7" -version = "5.0.5" [package.dependencies] ipython-genutils = "*" @@ -1092,34 +1097,25 @@ ipython-genutils = "*" test = ["pytest"] [[package]] -category = "main" -description = "An asynchronous networking framework written in Python" name = "twisted" +version = "21.2.0" +description = "An asynchronous networking framework written in Python" +category = "main" optional = false python-versions = ">=3.5.4" -version = "21.2.0" [package.dependencies] -Automat = ">=0.8.0" attrs = ">=19.2.0" +Automat = ">=0.8.0" constantly = ">=15.1" hyperlink = ">=17.1.1" +idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" -twisted-iocpsupport = ">=1.0.0,<1.1.0" +pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} +service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} +twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} "zope.interface" = ">=4.4.2" -[package.dependencies.idna] -optional = true -version = ">=2.4" - -[package.dependencies.pyopenssl] -optional = true -version = ">=16.0.0" - -[package.dependencies.service-identity] -optional = true -version = ">=18.1.0" - [package.extras] all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] @@ -1135,82 +1131,78 @@ tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] [[package]] -category = "main" -description = "An extension for use in the twisted I/O Completion Ports reactor." -marker = "platform_system == \"Windows\"" name = "twisted-iocpsupport" +version = "1.0.1" +description = "An extension for use in the twisted I/O Completion Ports reactor." +category = "main" optional = false python-versions = "*" -version = "1.0.1" [[package]] -category = "main" -description = "Compatibility API between asyncio/Twisted/Trollius" name = "txaio" +version = "20.12.1" +description = "Compatibility API between asyncio/Twisted/Trollius" +category = "main" optional = false python-versions = ">=3.6" -version = "20.12.1" [package.extras] all = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] twisted = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false python-versions = "*" -version = "3.7.4.3" [[package]] -category = "dev" -description = "URI templates" name = "uritemplate" +version = "3.0.1" +description = "URI templates" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.1" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.2" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" -version = "0.2.5" [[package]] -category = "main" -description = "Interfaces for Python" name = "zope.interface" +version = "5.2.0" +description = "Interfaces for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.2.0" - -[package.dependencies] -setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1218,17 +1210,17 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [[package]] -category = "main" -description = "" name = "zxcvbn" +version = "4.4.28" +description = "" +category = "main" optional = false python-versions = "*" -version = "4.4.28" [metadata] -content-hash = "4f00dc7966e89a916217e0c953484c9d8ea16a3c4e611cda6b39450c39c58518" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.8" +content-hash = "03736ef184253d4d8207c1eebe67fb82b99aad9624b4db40bcdb34fea0e65945" [metadata.files] aioredis = [ @@ -1259,6 +1251,9 @@ autobahn = [ {file = "autobahn-20.12.3-py2.py3-none-any.whl", hash = "sha256:52ee4236ff9a1fcbbd9500439dcf3284284b37f8a6b31ecc8a36e00cf9f95049"}, {file = "autobahn-20.12.3.tar.gz", hash = "sha256:410a93e0e29882c8b5d5ab05d220b07609b886ef5f23c0b8d39153254ffd6895"}, ] +autoflake = [ + {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, +] automat = [ {file = "Automat-20.2.0-py2.py3-none-any.whl", hash = "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"}, {file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"}, @@ -1599,20 +1594,39 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] msgpack = [ @@ -1728,8 +1742,11 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, ] ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, @@ -1773,6 +1790,10 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] pygments = [ {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, diff --git a/pyproject.toml b/pyproject.toml index 950ba9d9..497ee422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ django-stubs = "^1.7.0" black = "^20.8b1" djangorestframework-stubs = "^1.3.0" PyYAML = "^5.4.1" +autoflake = "^1.4" [tool.black] exclude = 'migrations' From 2d508e0771c6bf5fe54d2a4f391d5820e08e165d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 13 Apr 2021 21:39:28 +0100 Subject: [PATCH 028/185] Remove unused imports, since apparently PyCharm doesn't support linters --- src/andromeda/models.py | 1 - src/andromeda/tests.py | 1 - src/announcements/tests.py | 1 - src/authentication/serializers.py | 1 - src/authentication/tests.py | 1 - src/authentication/views.py | 1 - src/backend/urls.py | 1 - src/challenge/models.py | 1 - src/challenge/serializers.py | 2 -- src/experiments/models.py | 1 - src/experiments/views.py | 1 - src/leaderboard/tests.py | 2 +- src/member/migrations/0001_initial.py | 1 - src/member/tests.py | 1 - src/pages/tests.py | 1 - src/polaris/admin.py | 1 - src/polaris/models.py | 1 - src/polaris/tests.py | 1 - src/ractf/management/commands/insert_mega_dummy_data.py | 1 - src/ractf/models.py | 1 - src/ractf/tests.py | 1 - src/scorerecalculator/models.py | 1 - src/scorerecalculator/views.py | 1 - src/stats/apps.py | 7 +------ src/stats/signals.py | 2 +- src/stats/urls.py | 2 +- src/team/views.py | 1 - src/websockets/apps.py | 2 +- src/websockets/models.py | 1 - 29 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/andromeda/models.py b/src/andromeda/models.py index 71a83623..35e0d648 100644 --- a/src/andromeda/models.py +++ b/src/andromeda/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/src/andromeda/tests.py b/src/andromeda/tests.py index 7ce503c2..49290204 100644 --- a/src/andromeda/tests.py +++ b/src/andromeda/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/src/announcements/tests.py b/src/announcements/tests.py index 7ce503c2..49290204 100644 --- a/src/announcements/tests.py +++ b/src/announcements/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 7860d2ba..be0bebb4 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -12,7 +12,6 @@ from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException from backend.mail import send_email -from backend.response import FormattedResponse from backend.signals import register from plugins import providers from team.models import Team diff --git a/src/authentication/tests.py b/src/authentication/tests.py index a82d4726..c49b2f52 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -1,5 +1,4 @@ from unittest import mock -from unittest.mock import patch import pyotp from django.contrib.auth import get_user_model diff --git a/src/authentication/views.py b/src/authentication/views.py index eeba9dc0..a3ebc183 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -11,7 +11,6 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import permissions from rest_framework.generics import CreateAPIView, GenericAPIView, get_object_or_404 -from rest_framework.permissions import IsAuthenticated from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED from rest_framework.views import APIView diff --git a/src/backend/urls.py b/src/backend/urls.py index 5fe8bc70..48a394f4 100644 --- a/src/backend/urls.py +++ b/src/backend/urls.py @@ -13,7 +13,6 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from os import getenv from django.urls import path, include from django.conf import settings diff --git a/src/challenge/models.py b/src/challenge/models.py index d0e693b9..dc033343 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -12,7 +12,6 @@ Value, UniqueConstraint, Q, - Subquery, JSONField, ) from django.db.models.aggregates import Count diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index c6808deb..f9dc9e89 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,8 +1,6 @@ from collections import Counter import serpy -from django.core.cache import caches -from django.db import connection from rest_framework import serializers from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote diff --git a/src/experiments/models.py b/src/experiments/models.py index fd18c6ea..eadab150 100644 --- a/src/experiments/models.py +++ b/src/experiments/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/src/experiments/views.py b/src/experiments/views.py index 30f5c2aa..e116f68f 100644 --- a/src/experiments/views.py +++ b/src/experiments/views.py @@ -1,4 +1,3 @@ -from django.shortcuts import render from django.conf import settings from rest_framework.views import APIView diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 80b50d23..134d35e6 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -1,6 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN +from rest_framework.status import HTTP_200_OK from rest_framework.test import APITestCase from challenge.models import Score, Solve, Category, Challenge diff --git a/src/member/migrations/0001_initial.py b/src/member/migrations/0001_initial.py index 93ff0072..2e17c710 100644 --- a/src/member/migrations/0001_initial.py +++ b/src/member/migrations/0001_initial.py @@ -8,7 +8,6 @@ from django.db import migrations, models import django.db.models.deletion import django.utils.timezone -import member.models import secrets diff --git a/src/member/tests.py b/src/member/tests.py index e553c7dd..7201ed21 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -5,7 +5,6 @@ HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST, - HTTP_404_NOT_FOUND, ) from rest_framework.test import APITestCase diff --git a/src/pages/tests.py b/src/pages/tests.py index 7ce503c2..49290204 100644 --- a/src/pages/tests.py +++ b/src/pages/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/src/polaris/admin.py b/src/polaris/admin.py index 8c38f3f3..b97a94f6 100644 --- a/src/polaris/admin.py +++ b/src/polaris/admin.py @@ -1,3 +1,2 @@ -from django.contrib import admin # Register your models here. diff --git a/src/polaris/models.py b/src/polaris/models.py index 71a83623..35e0d648 100644 --- a/src/polaris/models.py +++ b/src/polaris/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/src/polaris/tests.py b/src/polaris/tests.py index 7ce503c2..49290204 100644 --- a/src/polaris/tests.py +++ b/src/polaris/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 9211c514..5a080ab3 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -3,7 +3,6 @@ from django.contrib.auth import get_user_model from django.core.management import BaseCommand -from django.db import transaction from challenge.models import Challenge, Category, Score, Solve from team.models import Team diff --git a/src/ractf/models.py b/src/ractf/models.py index 71a83623..35e0d648 100644 --- a/src/ractf/models.py +++ b/src/ractf/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/src/ractf/tests.py b/src/ractf/tests.py index 7ce503c2..49290204 100644 --- a/src/ractf/tests.py +++ b/src/ractf/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/src/scorerecalculator/models.py b/src/scorerecalculator/models.py index 71a83623..35e0d648 100644 --- a/src/scorerecalculator/models.py +++ b/src/scorerecalculator/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/src/scorerecalculator/views.py b/src/scorerecalculator/views.py index 76a71355..9840d865 100644 --- a/src/scorerecalculator/views.py +++ b/src/scorerecalculator/views.py @@ -6,7 +6,6 @@ from backend.response import FormattedResponse from challenge.models import Score -import config from team.models import Team diff --git a/src/stats/apps.py b/src/stats/apps.py index e09226c9..e5ab4ec9 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -1,12 +1,8 @@ import sys -from django.apps import AppConfig, apps -from django.db.models.signals import post_delete, post_save -from django.dispatch import receiver +from django.apps import AppConfig -from prometheus_client import Gauge -from backend.signals import websocket_connect, websocket_disconnect class StatsConfig(AppConfig): @@ -19,4 +15,3 @@ def ready(self): # Don't run stats-related logic if we haven't migrated yet return - import stats.signals diff --git a/src/stats/signals.py b/src/stats/signals.py index 92f14d33..86c14454 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -1,5 +1,5 @@ from django.dispatch import receiver -from django.db.models.signals import post_delete, post_save +from django.db.models.signals import post_delete from prometheus_client import Gauge from backend.signals import websocket_disconnect, websocket_connect, team_create, flag_score, register diff --git a/src/stats/urls.py b/src/stats/urls.py index 65d7656e..72ca837d 100644 --- a/src/stats/urls.py +++ b/src/stats/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import path from stats import views diff --git a/src/team/views.py b/src/team/views.py index 3d7403b6..d7afc672 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -1,4 +1,3 @@ -from django.db.models import Prefetch from django.http import Http404 from rest_framework import filters from rest_framework.generics import ( diff --git a/src/websockets/apps.py b/src/websockets/apps.py index 679ffabb..c455ca6c 100644 --- a/src/websockets/apps.py +++ b/src/websockets/apps.py @@ -6,4 +6,4 @@ class WebsocketsConfig(AppConfig): def ready(self): # noinspection PyUnresolvedReferences - import websockets.signals + pass diff --git a/src/websockets/models.py b/src/websockets/models.py index 71a83623..35e0d648 100644 --- a/src/websockets/models.py +++ b/src/websockets/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. From bb46c8337f11fa1b05036e74d988f7f7eb78cd51 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 13 Apr 2021 21:44:37 +0100 Subject: [PATCH 029/185] Re-add the {silly, dullard, person with Down Syndrome, person with developmental disabilities, delay, hold back} imports --- src/stats/apps.py | 1 + src/websockets/apps.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stats/apps.py b/src/stats/apps.py index e5ab4ec9..a47a02e6 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -15,3 +15,4 @@ def ready(self): # Don't run stats-related logic if we haven't migrated yet return + import stats.signals diff --git a/src/websockets/apps.py b/src/websockets/apps.py index c455ca6c..679ffabb 100644 --- a/src/websockets/apps.py +++ b/src/websockets/apps.py @@ -6,4 +6,4 @@ class WebsocketsConfig(AppConfig): def ready(self): # noinspection PyUnresolvedReferences - pass + import websockets.signals From 6be9b19e2a2a203c9e41b29654af5961fa77d0e1 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 13 Apr 2021 21:46:10 +0100 Subject: [PATCH 030/185] Add serpy to lockfile --- poetry.lock | 17 ++++++++++++++++- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index c07995f1..7530f0dd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1011,6 +1011,17 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] +[[package]] +name = "serpy" +version = "0.3.1" +description = "ridiculously fast object serialization" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + [[package]] name = "service-identity" version = "18.1.0" @@ -1220,7 +1231,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "03736ef184253d4d8207c1eebe67fb82b99aad9624b4db40bcdb34fea0e65945" +content-hash = "958c5c41e77f736bb86360e80268945ed3339246e7baff8796942ffddeb23525" [metadata.files] aioredis = [ @@ -1911,6 +1922,10 @@ sentry-sdk = [ {file = "sentry-sdk-0.16.5.tar.gz", hash = "sha256:e12eb1c2c01cd9e9cfe70608dbda4ef451f37ef0b7cbb92e5d43f87c341d6334"}, {file = "sentry_sdk-0.16.5-py2.py3-none-any.whl", hash = "sha256:d359609e23ec9360b61e5ffdfa417e2f6bca281bfb869608c98c169c7e64acd5"}, ] +serpy = [ + {file = "serpy-0.3.1-py2.py3-none-any.whl", hash = "sha256:750ded3df0671918b81d6efcab2b85cac12f9fcc2bce496c24a0ffa65d84b5da"}, + {file = "serpy-0.3.1.tar.gz", hash = "sha256:3772b2a9923fbf674000ff51abebf6ea8f0fca0a2cfcbfa0d63ff118193d1ec5"}, +] service-identity = [ {file = "service_identity-18.1.0-py2.py3-none-any.whl", hash = "sha256:001c0707759cb3de7e49c078a7c0c9cd12594161d3bf06b9c254fdcb1a60dc36"}, {file = "service_identity-18.1.0.tar.gz", hash = "sha256:0858a54aabc5b459d1aafa8a518ed2081a285087f349fe3e55197989232e2e2d"}, diff --git a/pyproject.toml b/pyproject.toml index 497ee422..ea00ec52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ django-prometheus = "^2.1.0" django-cachalot = "^2.3.5" cachalot = "^1.5.0" django-silk = "^4.1.0" +serpy = "^0.3.1" [tool.poetry.dev-dependencies] ipython = "^7.19.0" From cf2f96ad29affd11aa7d702f69e8718022dfb2d2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 28 May 2021 03:47:09 +0100 Subject: [PATCH 031/185] Co-authored-by: Jeremiah Boby --- Dockerfile | 4 +- poetry.lock | 876 +++++++++++-------- pyproject.toml | 9 +- src/authentication/tests.py | 28 +- src/backend/authentication.py | 4 +- src/backend/permissions.py | 2 +- src/backend/settings/local.py | 3 +- src/challenge/permissions.py | 6 +- src/challenge/sql.py | 10 +- src/challenge/tests.py | 4 +- src/challenge/views.py | 12 +- src/leaderboard/tests.py | 28 +- src/leaderboard/views.py | 14 +- src/member/serializers.py | 2 +- src/plugins/flag/lenient.py | 2 +- src/plugins/points/decay.py | 2 +- src/plugins/tests.py | 2 +- src/ractf/management/commands/copy_points.py | 2 +- src/stats/apps.py | 12 +- src/stats/signals.py | 12 +- src/team/permissions.py | 2 +- src/websockets/signals.py | 2 +- 22 files changed, 598 insertions(+), 440 deletions(-) diff --git a/Dockerfile b/Dockerfile index 08cf4256..a14c9a98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM python:3.8-slim +FROM python:3.9-slim ARG BUILD_DEPS="build-essential curl" RUN set -ex \ && apt-get update && apt-get -y --no-install-recommends install $BUILD_DEPS libpq-dev netcat git \ && rm -rf /var/lib/apt/lists/* \ - && curl -sSL "https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py" | POETRY_PREVIEW=1 python \ + && curl -sSL "https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py" | python \ && . $HOME/.poetry/env \ && poetry config virtualenvs.create false diff --git a/poetry.lock b/poetry.lock index 7530f0dd..f96c308c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,71 +1,81 @@ [[package]] -name = "aioredis" -version = "1.3.1" -description = "asyncio (PEP 3156) Redis support" category = "main" +description = "asyncio (PEP 3156) Redis support" +name = "aioredis" optional = false python-versions = "*" +version = "1.3.1" [package.dependencies] async-timeout = "*" hiredis = "*" [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" optional = false python-versions = "*" +version = "1.4.4" [[package]] -name = "appnope" -version = "0.1.2" -description = "Disable App Nap on macOS >= 10.9" category = "dev" +description = "Disable App Nap on macOS >= 10.9" +marker = "sys_platform == \"darwin\"" +name = "appnope" optional = false python-versions = "*" +version = "0.1.2" [[package]] -name = "asgiref" -version = "3.3.1" -description = "ASGI specs, helper code, and adapters" category = "main" +description = "ASGI specs, helper code, and adapters" +name = "asgiref" optional = false python-versions = ">=3.5" +version = "3.3.1" [package.extras] tests = ["pytest", "pytest-asyncio"] [[package]] -name = "async-timeout" -version = "3.0.1" -description = "Timeout context manager for asyncio programs" category = "main" +description = "Timeout context manager for asyncio programs" +name = "async-timeout" optional = false python-versions = ">=3.5.3" +version = "3.0.1" + +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" [[package]] -name = "attrs" -version = "20.3.0" -description = "Classes Without Boilerplate" category = "main" +description = "Classes Without Boilerplate" +name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3.0" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -name = "autobahn" -version = "20.12.3" -description = "WebSocket client & server library, WAMP real-time framework" category = "main" +description = "WebSocket client & server library, WAMP real-time framework" +name = "autobahn" optional = false python-versions = ">=3.6" +version = "20.12.3" [package.dependencies] cryptography = ">=2.9.2" @@ -85,23 +95,23 @@ twisted = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)"] xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] [[package]] -name = "autoflake" -version = "1.4" -description = "Removes unused imports and unused variables" category = "dev" +description = "Removes unused imports and unused variables" +name = "autoflake" optional = false python-versions = "*" +version = "1.4" [package.dependencies] pyflakes = ">=1.1.0" [[package]] -name = "automat" -version = "20.2.0" -description = "Self-service finite-state machines for the programmer on the go." category = "main" +description = "Self-service finite-state machines for the programmer on the go." +name = "automat" optional = false python-versions = "*" +version = "20.2.0" [package.dependencies] attrs = ">=19.2.0" @@ -111,32 +121,32 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -name = "autopep8" -version = "1.5.4" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "main" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +name = "autopep8" optional = false python-versions = "*" +version = "1.5.4" [package.dependencies] pycodestyle = ">=2.6.0" toml = "*" [[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" category = "dev" +description = "Specifications for callback functions passed in to an API" +name = "backcall" optional = false python-versions = "*" +version = "0.2.0" [[package]] -name = "black" -version = "20.8b1" -description = "The uncompromising code formatter." category = "dev" +description = "The uncompromising code formatter." +name = "black" optional = false python-versions = ">=3.6" +version = "20.8b1" [package.dependencies] appdirs = "*" @@ -153,12 +163,12 @@ colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -name = "boto3" -version = "1.16.43" -description = "The AWS SDK for Python" category = "main" +description = "The AWS SDK for Python" +name = "boto3" optional = false python-versions = "*" +version = "1.16.43" [package.dependencies] botocore = ">=1.19.43,<1.20.0" @@ -166,25 +176,28 @@ jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" [[package]] -name = "botocore" -version = "1.19.43" -description = "Low-level, data-driven core of boto 3." category = "main" +description = "Low-level, data-driven core of boto 3." +name = "botocore" optional = false python-versions = "*" +version = "1.19.43" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<1.27", markers = "python_version != \"3.4\""} + +[package.dependencies.urllib3] +python = "<3.4.0 || >=3.5.0" +version = ">=1.25.4,<1.27" [[package]] -name = "cachalot" -version = "1.5.0" -description = "Minimal persistent memoization cache" category = "main" +description = "Minimal persistent memoization cache" +name = "cachalot" optional = false python-versions = ">=3.5,<4.0" +version = "1.5.0" [package.dependencies] jsonpickle = "*" @@ -192,47 +205,47 @@ tinydb = ">=4,<5" tinydb-smartcache = "*" [[package]] -name = "certifi" -version = "2020.12.5" -description = "Python package for providing Mozilla's CA Bundle." category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" optional = false python-versions = "*" +version = "2020.12.5" [[package]] -name = "cffi" -version = "1.14.4" -description = "Foreign Function Interface for Python calling C code." category = "main" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" optional = false python-versions = "*" +version = "1.14.4" [package.dependencies] pycparser = "*" [[package]] -name = "channels" -version = "2.4.0" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." category = "main" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +name = "channels" optional = false python-versions = ">=3.5" +version = "2.4.0" [package.dependencies] +Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" -Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -name = "channels-redis" -version = "2.4.1" -description = "Redis-backed ASGI channel layer implementation" category = "main" +description = "Redis-backed ASGI channel layer implementation" +name = "channels-redis" optional = false python-versions = ">=3.6" +version = "2.4.1" [package.dependencies] aioredis = ">=1.0,<2.0" @@ -245,44 +258,45 @@ cryptography = ["cryptography (>=1.3.0)"] tests = ["cryptography (>=1.3.0)", "pytest (>=3.6.0,<3.7.0)", "pytest-asyncio (>=0.8,<1.0)", "async-generator (>=1.8,<2.0)", "async-timeout (>=2.0,<3.0)"] [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.0.0" [[package]] -name = "click" -version = "7.1.2" -description = "Composable command line interface toolkit" category = "dev" +description = "Composable command line interface toolkit" +name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" [[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.4" [[package]] -name = "constantly" -version = "15.1.0" -description = "Symbolic constants in Python" category = "main" +description = "Symbolic constants in Python" +name = "constantly" optional = false python-versions = "*" +version = "15.1.0" [[package]] -name = "coreapi" -version = "2.3.3" -description = "Python client library for Core API." category = "dev" +description = "Python client library for Core API." +name = "coreapi" optional = false python-versions = "*" +version = "2.3.3" [package.dependencies] coreschema = "*" @@ -291,77 +305,80 @@ requests = "*" uritemplate = "*" [[package]] -name = "coreschema" -version = "0.0.4" -description = "Core Schema." category = "dev" +description = "Core Schema." +name = "coreschema" optional = false python-versions = "*" +version = "0.0.4" [package.dependencies] jinja2 = "*" [[package]] -name = "coverage" -version = "5.3.1" -description = "Code coverage measurement for Python" category = "dev" +description = "Code coverage measurement for Python" +name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.3.1" [package.extras] toml = ["toml"] [[package]] -name = "cryptography" -version = "3.3.1" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +name = "cryptography" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +version = "3.3.1" [package.dependencies] cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] [[package]] -name = "daphne" -version = "2.5.0" -description = "Django ASGI (HTTP/WebSocket) server" category = "main" +description = "Django ASGI (HTTP/WebSocket) server" +name = "daphne" optional = false python-versions = "*" +version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" -twisted = {version = ">=18.7", extras = ["tls"]} + +[package.dependencies.twisted] +extras = ["tls"] +version = ">=18.7" [package.extras] -tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] [[package]] -name = "decorator" -version = "4.4.2" -description = "Decorators for Humans" category = "dev" +description = "Decorators for Humans" +name = "decorator" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" [[package]] -name = "django" -version = "3.1.4" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" optional = false python-versions = ">=3.6" +version = "3.1.4" [package.dependencies] asgiref = ">=3.2.10,<4" @@ -373,99 +390,99 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -name = "django-cachalot" -version = "2.3.5" -description = "Caches your Django ORM queries and automatically invalidates them." category = "main" +description = "Caches your Django ORM queries and automatically invalidates them." +name = "django-cachalot" optional = false python-versions = "*" +version = "2.3.5" [package.dependencies] Django = ">=2" [[package]] -name = "django-cors-headers" -version = "3.2.1" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." category = "main" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +name = "django-cors-headers" optional = false python-versions = ">=3.5" +version = "3.2.1" [package.dependencies] Django = ">=1.11" [[package]] -name = "django-filter" -version = "2.4.0" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." category = "main" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +name = "django-filter" optional = false python-versions = ">=3.5" +version = "2.4.0" [package.dependencies] Django = ">=2.2" [[package]] -name = "django-prometheus" -version = "2.1.0" -description = "Django middlewares to monitor your application with Prometheus.io." category = "main" +description = "Django middlewares to monitor your application with Prometheus.io." +name = "django-prometheus" optional = false python-versions = "*" +version = "2.1.0" [package.dependencies] prometheus-client = ">=0.7" [[package]] -name = "django-redis" -version = "4.11.0" -description = "Full featured redis cache backend for Django." category = "main" +description = "Full featured redis cache backend for Django." +name = "django-redis" optional = false python-versions = ">=3.5" +version = "4.11.0" [package.dependencies] Django = ">=1.11" redis = ">=2.10.0" [[package]] -name = "django-redis-cache" -version = "2.1.1" -description = "Redis Cache Backend for Django" category = "main" +description = "Redis Cache Backend for Django" +name = "django-redis-cache" optional = false python-versions = "*" +version = "2.1.1" [package.dependencies] redis = "<4.0" six = "*" [[package]] -name = "django-silk" -version = "4.1.0" -description = "Silky smooth profiling for the Django Framework" category = "main" +description = "Silky smooth profiling for the Django Framework" +name = "django-silk" optional = false python-versions = ">=3.5" +version = "4.1.0" [package.dependencies] -autopep8 = "*" Django = ">=2.2" -gprof2dot = ">=2017.09.19" Jinja2 = "*" Pygments = "*" +autopep8 = "*" +gprof2dot = ">=2017.09.19" python-dateutil = "*" pytz = "*" requests = "*" sqlparse = "*" [[package]] -name = "django-storages" -version = "1.9.1" -description = "Support for many storage backends in Django" category = "main" +description = "Support for many storage backends in Django" +name = "django-storages" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.9.1" [package.dependencies] Django = ">=1.11" @@ -479,12 +496,12 @@ libcloud = ["apache-libcloud"] sftp = ["paramiko"] [[package]] -name = "django-stubs" -version = "1.7.0" -description = "Mypy stubs for Django" category = "dev" +description = "Mypy stubs for Django" +name = "django-stubs" optional = false python-versions = ">=3.6" +version = "1.7.0" [package.dependencies] django = "*" @@ -492,35 +509,35 @@ mypy = ">=0.790" typing-extensions = "*" [[package]] -name = "django-zxcvbn-password-validator" -version = "1.3.0" -description = "A translatable password validator for django, based on zxcvbn-python." category = "main" +description = "A translatable password validator for django, based on zxcvbn-python." +name = "django-zxcvbn-password-validator" optional = false python-versions = "*" +version = "1.3.0" [package.dependencies] Django = ">=2.0" zxcvbn = "*" [[package]] -name = "djangorestframework" -version = "3.11.1" -description = "Web APIs for Django, made easy." category = "main" +description = "Web APIs for Django, made easy." +name = "djangorestframework" optional = false python-versions = ">=3.5" +version = "3.11.1" [package.dependencies] django = ">=1.11" [[package]] -name = "djangorestframework-stubs" -version = "1.3.0" -description = "PEP-484 stubs for django-rest-framework" category = "dev" +description = "PEP-484 stubs for django-rest-framework" +name = "djangorestframework-stubs" optional = false python-versions = ">=3.6" +version = "1.3.0" [package.dependencies] coreapi = ">=2.0.0" @@ -530,20 +547,23 @@ requests = ">=2.0.0" typing-extensions = ">=3.7.2" [[package]] -name = "gprof2dot" -version = "2019.11.30" -description = "Generate a dot graph from the output of several profilers." category = "main" +description = "Generate a dot graph from the output of several profilers." +name = "gprof2dot" optional = false python-versions = "*" +version = "2019.11.30" [[package]] -name = "gunicorn" -version = "20.0.4" -description = "WSGI HTTP Server for UNIX" category = "main" +description = "WSGI HTTP Server for UNIX" +name = "gunicorn" optional = false python-versions = ">=3.4" +version = "20.0.4" + +[package.dependencies] +setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.9.7)"] @@ -552,61 +572,70 @@ setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] [[package]] -name = "hiredis" -version = "1.1.0" -description = "Python wrapper for hiredis" category = "main" +description = "Python wrapper for hiredis" +name = "hiredis" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" [[package]] -name = "hyperlink" -version = "20.0.1" -description = "A featureful, immutable, and correct URL for Python." category = "main" +description = "A featureful, immutable, and correct URL for Python." +name = "hyperlink" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.0.1" [package.dependencies] idna = ">=2.5" [[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.10" [[package]] -name = "incremental" -version = "17.5.0" -description = "" category = "main" +description = "" +name = "incremental" optional = false python-versions = "*" +version = "17.5.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] -name = "ipython" -version = "7.19.0" -description = "IPython: Productive Interactive Computing" category = "dev" +description = "iniconfig: brain-dead simple config-ini parsing" +name = "iniconfig" +optional = false +python-versions = "*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "IPython: Productive Interactive Computing" +name = "ipython" optional = false python-versions = ">=3.7" +version = "7.19.0" [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} +appnope = "*" backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} +colorama = "*" decorator = "*" jedi = ">=0.10" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pexpect = ">4.3" pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" +setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -621,43 +650,43 @@ qtconsole = ["qtconsole"] test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] [[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" category = "dev" +description = "Vestigial utilities from IPython" +name = "ipython-genutils" optional = false python-versions = "*" +version = "0.2.0" [[package]] -name = "itypes" -version = "1.2.0" -description = "Simple immutable types for python." category = "dev" +description = "Simple immutable types for python." +name = "itypes" optional = false python-versions = "*" +version = "1.2.0" [[package]] -name = "jedi" -version = "0.18.0" -description = "An autocompletion tool for Python that can be used for text editors." category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +name = "jedi" optional = false python-versions = ">=3.6" +version = "0.18.0" [package.dependencies] parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] -name = "jinja2" -version = "2.11.2" -description = "A very fast and expressive template engine." category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -666,49 +695,49 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -name = "jmespath" -version = "0.10.0" -description = "JSON Matching Expressions" category = "main" +description = "JSON Matching Expressions" +name = "jmespath" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.10.0" [[package]] -name = "jsonpickle" -version = "2.0.0" -description = "Python library for serializing any arbitrary object graph into JSON" category = "main" +description = "Python library for serializing any arbitrary object graph into JSON" +name = "jsonpickle" optional = false python-versions = ">=2.7" +version = "2.0.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] +testing = ["coverage (<5)", "pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] "testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] [[package]] -name = "markupsafe" -version = "1.1.1" -description = "Safely add untrusted strings to HTML/XML markup." category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" [[package]] -name = "msgpack" -version = "0.6.2" -description = "MessagePack (de)serializer." category = "main" +description = "MessagePack (de)serializer." +name = "msgpack" optional = false python-versions = "*" +version = "0.6.2" [[package]] -name = "mypy" -version = "0.790" -description = "Optional static typing for Python" category = "dev" +description = "Optional static typing for Python" +name = "mypy" optional = false python-versions = ">=3.5" +version = "0.790" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -719,159 +748,191 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" optional = false python-versions = "*" +version = "0.4.3" [[package]] -name = "newrelic" -version = "5.24.0.153" -description = "New Relic Python Agent" category = "main" +description = "New Relic Python Agent" +name = "newrelic" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "5.24.0.153" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] [[package]] -name = "parso" -version = "0.8.1" -description = "A Python Parser" category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.9" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +category = "dev" +description = "A Python Parser" +name = "parso" optional = false python-versions = ">=3.6" +version = "0.8.1" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] -name = "pathspec" -version = "0.8.1" -description = "Utility library for gitignore style pattern matching of file paths." category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.8.1" [[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "sys_platform != \"win32\"" +name = "pexpect" optional = false python-versions = "*" +version = "4.8.0" [package.dependencies] ptyprocess = ">=0.5" [[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" name = "pickleshare" +optional = false +python-versions = "*" version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" + +[[package]] category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.extras] +dev = ["pre-commit", "tox"] [[package]] -name = "prometheus-client" -version = "0.9.0" -description = "Python client for the Prometheus monitoring system." category = "main" +description = "Python client for the Prometheus monitoring system." +name = "prometheus-client" optional = false python-versions = "*" +version = "0.9.0" [package.extras] twisted = ["twisted"] [[package]] -name = "prompt-toolkit" -version = "3.0.8" -description = "Library for building powerful interactive command lines in Python" category = "dev" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" optional = false python-versions = ">=3.6.1" +version = "3.0.8" [package.dependencies] wcwidth = "*" [[package]] -name = "psycopg2-binary" -version = "2.8.6" -description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg2-binary" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "2.8.6" [[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "sys_platform != \"win32\"" name = "ptyprocess" +optional = false +python-versions = "*" version = "0.6.0" -description = "Run a subprocess in a pseudo terminal" + +[[package]] category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.10.0" [[package]] -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" category = "main" +description = "ASN.1 types and codecs" +name = "pyasn1" optional = false python-versions = "*" +version = "0.4.8" [[package]] -name = "pyasn1-modules" -version = "0.2.8" -description = "A collection of ASN.1-based protocols modules." category = "main" +description = "A collection of ASN.1-based protocols modules." +name = "pyasn1-modules" optional = false python-versions = "*" +version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -name = "pycodestyle" -version = "2.6.0" -description = "Python style guide checker" category = "main" +description = "Python style guide checker" +name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" [[package]] -name = "pycparser" -version = "2.20" -description = "C parser in Python" category = "main" +description = "C parser in Python" +name = "pycparser" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" [[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" category = "dev" +description = "passive checker of Python programs" +name = "pyflakes" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.3.1" [[package]] -name = "pygments" -version = "2.7.3" -description = "Pygments is a syntax highlighting package written in Python." category = "main" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" optional = false python-versions = ">=3.5" +version = "2.7.3" [[package]] -name = "pyopenssl" -version = "20.0.1" -description = "Python wrapper module around the OpenSSL library" category = "main" +description = "Python wrapper module around the OpenSSL library" +name = "pyopenssl" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "20.0.1" [package.dependencies] cryptography = ">=3.2" @@ -882,74 +943,136 @@ docs = ["sphinx", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] -name = "pyotp" -version = "2.3.0" -description = "Python One Time Password Library" category = "main" +description = "Python One Time Password Library" +name = "pyotp" optional = false python-versions = "*" +version = "2.3.0" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.6" +version = "6.2.4" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=19.2.0" +colorama = "*" +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "dev" +description = "Pytest plugin for measuring coverage." +name = "pytest-cov" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.12.0" + +[package.dependencies] +pytest = ">=4.6" + +[package.dependencies.coverage] +extras = ["toml"] +version = ">=5.2.1" + +[package.extras] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] + +[[package]] +category = "dev" +description = "A Django plugin for pytest." +name = "pytest-django" +optional = false +python-versions = ">=3.5" +version = "4.3.0" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)"] [[package]] -name = "python-dateutil" -version = "2.8.1" -description = "Extensions to the standard Python datetime module" category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -name = "python-http-client" -version = "3.3.1" -description = "HTTP REST client, simplified for Python" category = "main" +description = "HTTP REST client, simplified for Python" +name = "python-http-client" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.3.1" [[package]] -name = "pytz" -version = "2020.5" -description = "World timezone definitions, modern and historical" category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" optional = false python-versions = "*" +version = "2020.5" [[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" category = "dev" +description = "YAML parser and emitter for Python" +name = "pyyaml" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +version = "5.4.1" [[package]] -name = "redis" -version = "3.5.3" -description = "Python client for Redis key-value store" category = "main" +description = "Python client for Redis key-value store" +name = "redis" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -name = "regex" -version = "2020.11.13" -description = "Alternative regular expression module, to replace re." category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" optional = false python-versions = "*" +version = "2020.11.13" [[package]] -name = "requests" -version = "2.25.1" -description = "Python HTTP for Humans." category = "main" +description = "Python HTTP for Humans." +name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.25.1" [package.dependencies] certifi = ">=2017.4.17" @@ -959,38 +1082,38 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] -name = "s3transfer" -version = "0.3.3" -description = "An Amazon S3 Transfer Manager" category = "main" +description = "An Amazon S3 Transfer Manager" +name = "s3transfer" optional = false python-versions = "*" +version = "0.3.3" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [[package]] -name = "sendgrid" -version = "6.4.8" -description = "Twilio SendGrid library for Python" category = "main" +description = "Twilio SendGrid library for Python" +name = "sendgrid" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "6.4.8" [package.dependencies] python-http-client = ">=3.2.1" starkbank-ecdsa = ">=1.0.0" [[package]] -name = "sentry-sdk" -version = "0.16.5" -description = "Python client for Sentry (https://sentry.io)" category = "main" +description = "Python client for Sentry (https://sentry.io)" +name = "sentry-sdk" optional = false python-versions = "*" +version = "0.16.5" [package.dependencies] certifi = "*" @@ -1012,23 +1135,23 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] [[package]] -name = "serpy" -version = "0.3.1" -description = "ridiculously fast object serialization" category = "main" +description = "ridiculously fast object serialization" +name = "serpy" optional = false python-versions = "*" +version = "0.3.1" [package.dependencies] six = "*" [[package]] -name = "service-identity" -version = "18.1.0" -description = "Service identity verification for pyOpenSSL & cryptography." category = "main" +description = "Service identity verification for pyOpenSSL & cryptography." +name = "service-identity" optional = false python-versions = "*" +version = "18.1.0" [package.dependencies] attrs = ">=16.0.0" @@ -1043,63 +1166,63 @@ idna = ["idna"] tests = ["coverage (>=4.2.0)", "pytest"] [[package]] -name = "six" -version = "1.15.0" -description = "Python 2 and 3 compatibility utilities" category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" [[package]] -name = "sqlparse" -version = "0.4.1" -description = "A non-validating SQL parser." category = "main" +description = "A non-validating SQL parser." +name = "sqlparse" optional = false python-versions = ">=3.5" +version = "0.4.1" [[package]] -name = "starkbank-ecdsa" -version = "1.1.0" -description = "A lightweight and fast pure python ECDSA library" category = "main" +description = "A lightweight and fast pure python ECDSA library" +name = "starkbank-ecdsa" optional = false python-versions = "*" +version = "1.1.0" [[package]] -name = "tinydb" -version = "4.4.0" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" +name = "tinydb" optional = false python-versions = ">=3.5,<4.0" +version = "4.4.0" [[package]] -name = "tinydb-smartcache" -version = "2.0.0" -description = "A smarter query cache for TinyDB" category = "main" +description = "A smarter query cache for TinyDB" +name = "tinydb-smartcache" optional = false python-versions = ">=3.5,<4.0" +version = "2.0.0" [package.dependencies] tinydb = ">=4.0,<5.0" [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" category = "main" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.10.2" [[package]] -name = "traitlets" -version = "5.0.5" -description = "Traitlets Python configuration system" category = "dev" +description = "Traitlets Python configuration system" +name = "traitlets" optional = false python-versions = ">=3.7" +version = "5.0.5" [package.dependencies] ipython-genutils = "*" @@ -1108,25 +1231,34 @@ ipython-genutils = "*" test = ["pytest"] [[package]] -name = "twisted" -version = "21.2.0" -description = "An asynchronous networking framework written in Python" category = "main" +description = "An asynchronous networking framework written in Python" +name = "twisted" optional = false python-versions = ">=3.5.4" +version = "21.2.0" [package.dependencies] -attrs = ">=19.2.0" Automat = ">=0.8.0" +attrs = ">=19.2.0" constantly = ">=15.1" hyperlink = ">=17.1.1" -idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" -pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} -service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} -twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} +twisted-iocpsupport = ">=1.0.0,<1.1.0" "zope.interface" = ">=4.4.2" +[package.dependencies.idna] +optional = true +version = ">=2.4" + +[package.dependencies.pyopenssl] +optional = true +version = ">=16.0.0" + +[package.dependencies.service-identity] +optional = true +version = ">=18.1.0" + [package.extras] all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] @@ -1142,78 +1274,82 @@ tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] [[package]] -name = "twisted-iocpsupport" -version = "1.0.1" -description = "An extension for use in the twisted I/O Completion Ports reactor." category = "main" +description = "An extension for use in the twisted I/O Completion Ports reactor." +marker = "platform_system == \"Windows\"" +name = "twisted-iocpsupport" optional = false python-versions = "*" +version = "1.0.1" [[package]] -name = "txaio" -version = "20.12.1" -description = "Compatibility API between asyncio/Twisted/Trollius" category = "main" +description = "Compatibility API between asyncio/Twisted/Trollius" +name = "txaio" optional = false python-versions = ">=3.6" +version = "20.12.1" [package.extras] all = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] twisted = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] [[package]] -name = "typed-ast" -version = "1.4.1" -description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" optional = false python-versions = "*" +version = "1.4.1" [[package]] -name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" optional = false python-versions = "*" +version = "3.7.4.3" [[package]] -name = "uritemplate" -version = "3.0.1" -description = "URI templates" category = "dev" +description = "URI templates" +name = "uritemplate" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.0.1" [[package]] -name = "urllib3" -version = "1.26.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.26.2" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" optional = false python-versions = "*" +version = "0.2.5" [[package]] -name = "zope.interface" -version = "5.2.0" -description = "Interfaces for Python" category = "main" +description = "Interfaces for Python" +name = "zope.interface" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.2.0" + +[package.dependencies] +setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1221,17 +1357,17 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [[package]] -name = "zxcvbn" -version = "4.4.28" -description = "" category = "main" +description = "" +name = "zxcvbn" optional = false python-versions = "*" +version = "4.4.28" [metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "958c5c41e77f736bb86360e80268945ed3339246e7baff8796942ffddeb23525" +content-hash = "63be3bb11534daba55f8cd13d01c6ac94f1ca5fd00176ea0df1f04c2cd3119b3" +lock-version = "1.0" +python-versions = "^3.9" [metadata.files] aioredis = [ @@ -1254,6 +1390,10 @@ async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] attrs = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, @@ -1558,6 +1698,10 @@ incremental = [ {file = "incremental-17.5.0-py2.py3-none-any.whl", hash = "sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f"}, {file = "incremental-17.5.0.tar.gz", hash = "sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] ipython = [ {file = "ipython-7.19.0-py3-none-any.whl", hash = "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f"}, {file = "ipython-7.19.0.tar.gz", hash = "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"}, @@ -1605,39 +1749,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] msgpack = [ @@ -1698,6 +1823,10 @@ newrelic = [ {file = "newrelic-5.24.0.153-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:76ca893803d7a738844e5a49f51bde36fa98c8f55c203d2bf9a7f246770689c6"}, {file = "newrelic-5.24.0.153.tar.gz", hash = "sha256:a8ef9509258a8f0c952fe36c7d3c774552b65a5ca5e0348694b651170b8472dc"}, ] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] parso = [ {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, @@ -1714,6 +1843,10 @@ pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] prometheus-client = [ {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"}, {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, @@ -1753,16 +1886,17 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, ] ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, ] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, @@ -1817,6 +1951,22 @@ pyotp = [ {file = "pyotp-2.3.0-py2.py3-none-any.whl", hash = "sha256:c88f37fd47541a580b744b42136f387cdad481b560ef410c0d85c957eb2a2bc0"}, {file = "pyotp-2.3.0.tar.gz", hash = "sha256:fc537e8acd985c5cbf51e11b7d53c42276fee017a73aec7c07380695671ca1a1"}, ] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +pytest-cov = [ + {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, + {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, +] +pytest-django = [ + {file = "pytest-django-4.3.0.tar.gz", hash = "sha256:d1c6758a592fb0ef8abaa2fe12dd28858c1dcfc3d466102ffe52aa8934733dca"}, + {file = "pytest_django-4.3.0-py3-none-any.whl", hash = "sha256:f96c4556f4e7b15d987dd1dcc1d1526df81d40c1548d31ce840d597ed2be8c46"}, +] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, diff --git a/pyproject.toml b/pyproject.toml index ea00ec52..bf474d82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "The Django backend for RACTF." authors = ["RACTF Admins "] [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" channels-redis = "2.4.1" Django = "^3.1" django-cors-headers = "3.2.1" @@ -36,6 +36,13 @@ black = "^20.8b1" djangorestframework-stubs = "^1.3.0" PyYAML = "^5.4.1" autoflake = "^1.4" +pytest = "^6.2.4" +pytest-cov = "^2.12.0" +pytest-django = "^4.3.0" + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "backend.settings.local" +python_files = "tests.py test_*.py *_tests.py" [tool.black] exclude = 'migrations' diff --git a/src/authentication/tests.py b/src/authentication/tests.py index c49b2f52..db1df418 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -91,7 +91,7 @@ def test_register_duplicate_email(self): @mock.patch('time.time', side_effect=get_fake_time) def test_register_closed(self, mock_obj): - config.set('enable_prelogin', False) + config.config.set('enable_prelogin', False) data = { 'username': 'user6', 'password': 'uO7*$E@0ngqL', @@ -99,7 +99,7 @@ def test_register_closed(self, mock_obj): } response = self.client.post(reverse('register'), data) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - config.set('enable_prelogin', True) + config.config.set('enable_prelogin', True) def test_register_admin(self): data = { @@ -142,14 +142,14 @@ def test_register_invalid_email(self): self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) def test_register_teams_disabled(self): - config.set('enable_teams', False) + config.config.set('enable_teams', False) data = { 'username': 'user10', 'password': 'uO7*$E@0ngqL', 'email': 'user10@example.com', } response = self.client.post(reverse('register'), data) - config.set('enable_teams', True) + config.config.set('enable_teams', True) self.assertEquals(response.status_code, HTTP_201_CREATED) self.assertEquals(get_user_model().objects.get(username='user10').team.name, 'user10') @@ -210,7 +210,7 @@ class InviteRequiredRegistrationTestCase(APITestCase): def setUp(self): RegistrationView.throttle_scope = '' - config.set('invite_required', True) + config.config.set('invite_required', True) InviteCode(code='test1', max_uses=10).save() InviteCode(code='test2', max_uses=1).save() InviteCode(code='test3', max_uses=1).save() @@ -225,7 +225,7 @@ def setUp(self): InviteCode(code='test4', max_uses=1, auto_team=team).save() def tearDown(self): - config.set('invite_required', False) + config.config.set('invite_required', False) def test_register_invite_required_missing_invite(self): data = { @@ -363,9 +363,9 @@ def test_login_login_closed(self, mock_obj): 'username': 'login-test', 'password': 'password', } - config.set('enable_prelogin', False) + config.config.set('enable_prelogin', False) response = self.client.post(reverse('login'), data) - config.set('enable_prelogin', True) + config.config.set('enable_prelogin', True) self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_inactive(self): @@ -662,26 +662,26 @@ def test_password_reset_weak_password(self): self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) def test_password_reset_login_disabled(self): - config.set('enable_login', False) + config.config.set('enable_login', False) data = { 'uid': self.user.id, 'token': 'testtoken', 'password': 'uO7*$E@0ngqL', } response = self.client.post(reverse('do-password-reset'), data) - config.set('enable_login', True) + config.config.set('enable_login', True) self.assertFalse('token' in response.data['d']) @mock.patch('time.time', side_effect=get_fake_time) def test_password_reset_cant_login_yet(self, obj): - config.set('enable_prelogin', False) + config.config.set('enable_prelogin', False) data = { 'uid': self.user.id, 'token': 'testtoken', 'password': 'uO7*$E@0ngqL', } response = self.client.post(reverse('do-password-reset'), data) - config.set('enable_prelogin', True) + config.config.set('enable_prelogin', True) self.assertFalse('token' in response.data['d']) @@ -711,14 +711,14 @@ def test_email_verify_invalid(self): self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) def test_email_verify_nologin(self): - config.set("enable_login", False) + config.config.set("enable_login", False) data = { 'uid': self.user.id, 'token': self.user.email_token, } response = self.client.post(reverse('verify-email'), data) - config.set("enable_login", False) + config.config.set("enable_login", False) self.assertEquals(response.data["d"], "") def test_email_verify_twice(self): diff --git a/src/backend/authentication.py b/src/backend/authentication.py index 6bc29c21..07efb979 100644 --- a/src/backend/authentication.py +++ b/src/backend/authentication.py @@ -14,9 +14,9 @@ def authenticate(self, request): user, token = x if user.is_staff and not user.should_deny_admin(): return user, token - if config.get("enable_maintenance_mode"): + if config.config.get("enable_maintenance_mode"): return None - if not config.get("enable_bot_users") and user.is_bot: + if not config.config.get("enable_bot_users") and user.is_bot: return None if token.user_id != token.owner_id: request.sudo = True diff --git a/src/backend/permissions.py b/src/backend/permissions.py index cecbd3b6..cc4565b2 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -32,7 +32,7 @@ def has_permission(self, request, view): class IsCompetitionOpen(permissions.BasePermission): def has_permission(self, request, view): - return config.get("start_time") <= time.time() + return config.config.get("start_time") <= time.time() class IsBot(permissions.BasePermission): diff --git a/src/backend/settings/local.py b/src/backend/settings/local.py index 92b1f10d..e1988e0a 100644 --- a/src/backend/settings/local.py +++ b/src/backend/settings/local.py @@ -1,4 +1,5 @@ from . import * -MAIL["SEND"] = False +DOMAIN = "localhost" +MAIL["SEND"] = False \ No newline at end of file diff --git a/src/challenge/permissions.py b/src/challenge/permissions.py index 76e11873..ad3c972e 100644 --- a/src/challenge/permissions.py +++ b/src/challenge/permissions.py @@ -8,9 +8,9 @@ class CompetitionOpen(permissions.BasePermission): def has_permission(self, request, view): return (request.user.is_staff and not request.user.should_deny_admin()) or ( - config.get("start_time") <= time.time() + config.config.get("start_time") <= time.time() and ( - config.get("enable_view_challenges_after_competion") - or time.time() <= config.get("end_time") + config.config.get("enable_view_challenges_after_competion") + or time.time() <= config.config.get("end_time") ) ) diff --git a/src/challenge/sql.py b/src/challenge/sql.py index 91612572..2a68d428 100644 --- a/src/challenge/sql.py +++ b/src/challenge/sql.py @@ -1,13 +1,13 @@ from django.core.cache import caches from django.db import connection -from config import config +import config def get_solve_counts(): cache = caches['default'] solve_counts = cache.get('solve_counts') - if solve_counts is not None and config.get('enable_caching'): + if solve_counts is not None and config.config.get('enable_caching'): return solve_counts with connection.cursor() as cursor: cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') @@ -19,7 +19,7 @@ def get_solve_counts(): def get_incorrect_solve_counts(): cache = caches['default'] solve_counts = cache.get('incorrect_solve_counts') - if solve_counts is not None and config.get('enable_caching'): + if solve_counts is not None and config.config.get('enable_caching'): return solve_counts with connection.cursor() as cursor: cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') @@ -31,7 +31,7 @@ def get_incorrect_solve_counts(): def get_positive_votes(): cache = caches['default'] positive_votes = cache.get('positive_votes') - if positive_votes is not None and config.get('enable_caching'): + if positive_votes is not None and config.config.get('enable_caching'): return positive_votes with connection.cursor() as cursor: cursor.execute( @@ -44,7 +44,7 @@ def get_positive_votes(): def get_negative_votes(): cache = caches['default'] negative_votes = cache.get('negative_votes') - if negative_votes is not None and config.get('enable_caching'): + if negative_votes is not None and config.config.get('enable_caching'): return negative_votes with connection.cursor() as cursor: cursor.execute( diff --git a/src/challenge/tests.py b/src/challenge/tests.py index df04fd75..1e508d0c 100644 --- a/src/challenge/tests.py +++ b/src/challenge/tests.py @@ -147,9 +147,9 @@ def test_is_not_solved(self): self.assertFalse(self.challenge1.is_solved(user=self.user)) def test_submission_disabled(self): - config.set('enable_flag_submission', False) + config.config.set('enable_flag_submission', False) response = self.solve_challenge() - config.set('enable_flag_submission', True) + config.config.set('enable_flag_submission', True) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) def test_submission_malformed(self): diff --git a/src/challenge/views.py b/src/challenge/views.py index d5931e26..54633ac6 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -72,7 +72,7 @@ def get_queryset(self): )), to_attr='hints'), Prefetch('file_set', queryset=File.objects.all(), to_attr='files'), Prefetch('tag_set', - queryset=Tag.objects.all() if time.time() > config.get('end_time') else Tag.objects.filter( + queryset=Tag.objects.all() if time.time() > config.config.get('end_time') else Tag.objects.filter( post_competition=False), to_attr='tags'), 'hint_set__uses').select_related('first_blood') if self.request.user.is_staff: @@ -88,7 +88,7 @@ def list(self, request, *args, **kwargs): cache = caches['default'] categories = cache.get(get_cache_key(request.user)) cache_hit = categories is not None - if categories is None or not config.get('enable_caching'): + if categories is None or not config.config.get('enable_caching'): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) categories = serializer.data @@ -226,8 +226,8 @@ class FlagSubmitView(APIView): throttle_scope = 'flag_submit' def post(self, request): - if not config.get('enable_flag_submission') or \ - (not config.get('enable_flag_submission_after_competition') and time.time() > config.get('end_time')): + if not config.config.get('enable_flag_submission') or \ + (not config.config.get('enable_flag_submission_after_competition') and time.time() > config.config.get('end_time')): return FormattedResponse(m='flag_submission_disabled', status=HTTP_403_FORBIDDEN) with transaction.atomic(): @@ -281,8 +281,8 @@ class FlagCheckView(APIView): throttle_scope = 'flag_submit' def post(self, request): - if not config.get('enable_flag_submission') or \ - (not config.get('enable_flag_submission_after_competition') and time.time() > config.get('end_time')): + if not config.config.get('enable_flag_submission') or \ + (not config.config.get('enable_flag_submission_after_competition') and time.time() > config.config.get('end_time')): return FormattedResponse(m='flag_submission_disabled', status=HTTP_403_FORBIDDEN) team = Team.objects.get(id=request.user.team.id) user = get_user_model().objects.get(id=request.user.id) diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 134d35e6..97189aae 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -50,9 +50,9 @@ def test_authed_access(self): self.assertEquals(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.set("enable_scoreboard", False) + config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-graph')) - config.set("enable_scoreboard", True) + config.config.set("enable_scoreboard", True) self.assertEquals(response.data["d"], {}) def test_format(self): @@ -74,9 +74,9 @@ def test_list_sorting(self): def test_user_only(self): populate() - config.set("enable_teams", False) + config.config.set("enable_teams", False) response = self.client.get(reverse('leaderboard-graph')) - config.set("enable_teams", True) + config.config.set("enable_teams", True) self.assertEquals(len(response.data['d']['user']), 10) self.assertEquals(response.data['d']['user'][0]['points'], 1400) self.assertNotIn("team", response.data['d'].keys()) @@ -101,9 +101,9 @@ def test_authed(self): self.assertEquals(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.set("enable_scoreboard", False) + config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-user')) - config.set("enable_scoreboard", True) + config.config.set("enable_scoreboard", True) self.assertEquals(response.data["d"], {}) def test_length(self): @@ -138,9 +138,9 @@ def test_authed(self): self.assertEquals(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.set("enable_scoreboard", False) + config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-team')) - config.set("enable_scoreboard", True) + config.config.set("enable_scoreboard", True) self.assertEquals(response.data["d"], {}) def test_length(self): @@ -173,15 +173,15 @@ def test_authed(self): self.assertEquals(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.set("enable_scoreboard", False) + config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-ctftime')) - config.set("enable_scoreboard", True) + config.config.set("enable_scoreboard", True) self.assertEquals(response.data, {}) def test_disabled_ctftime(self): - config.set("enable_ctftime", False) + config.config.set("enable_ctftime", False) response = self.client.get(reverse('leaderboard-ctftime')) - config.set("enable_ctftime", True) + config.config.set("enable_ctftime", True) self.assertEquals(response.data, {}) def test_length(self): @@ -236,7 +236,7 @@ def test_order(self): self.assertEquals(points, sorted(points, reverse=True)) def test_disabled_scoreboard(self): - config.set("enable_scoreboard", False) + config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-matrix-list')) - config.set("enable_scoreboard", True) + config.config.set("enable_scoreboard", True) self.assertEquals(response.data['d'], {}) diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index c5abbe6b..3a9e7580 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -17,16 +17,16 @@ def should_hide_scoreboard(): - return not config.get('enable_scoreboard') and (config.get('hide_scoreboard_at') == -1 or - config.get('hide_scoreboard_at') > time.time() or - config.get('end_time') > time.time()) + return not config.config.get('enable_scoreboard') and (config.config.get('hide_scoreboard_at') == -1 or + config.config.get('hide_scoreboard_at') > time.time() or + config.config.get('end_time') > time.time()) class CTFTimeListView(APIView): renderer_classes = (JSONRenderer, BrowsableAPIRenderer,) def get(self, request, *args, **kwargs): - if should_hide_scoreboard() or not config.get('enable_ctftime'): + if should_hide_scoreboard() or not config.config.get('enable_ctftime'): return Response({}) teams = Team.objects.visible().ranked() return Response({"standings": CTFTimeSerializer(teams, many=True).data}) @@ -41,10 +41,10 @@ def get(self, request, *args, **kwargs): cache = caches['default'] cached_leaderboard = cache.get('leaderboard_graph') - if cached_leaderboard is not None and config.get('enable_caching'): + if cached_leaderboard is not None and config.config.get('enable_caching'): return FormattedResponse(cached_leaderboard) - graph_members = config.get('graph_members') + graph_members = config.config.get('graph_members') top_teams = Team.objects.visible().ranked()[:graph_members] top_users = get_user_model().objects.filter(is_visible=True).order_by('-leaderboard_points', 'last_score')[ :graph_members] @@ -57,7 +57,7 @@ def get(self, request, *args, **kwargs): user_serializer = LeaderboardUserScoreSerializer(user_scores, many=True) team_serializer = LeaderboardTeamScoreSerializer(team_scores, many=True) response = {'user': user_serializer.data} - if config.get('enable_teams'): + if config.config.get('enable_teams'): response['team'] = team_serializer.data cache.set('leaderboard_graph', response, 15) diff --git a/src/member/serializers.py b/src/member/serializers.py index ca1dc157..4a67a4cb 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -75,7 +75,7 @@ def validate_email(self, value): return value def update(self, instance, validated_data): - if not config.get("enable_teams"): + if not config.config.get("enable_teams"): if instance.team: instance.team.name = validated_data.get("username", instance.username) return super(SelfSerializer, self).update(instance, validated_data) diff --git a/src/plugins/flag/lenient.py b/src/plugins/flag/lenient.py index 5d9c7c34..fcdafb17 100644 --- a/src/plugins/flag/lenient.py +++ b/src/plugins/flag/lenient.py @@ -16,7 +16,7 @@ def strip_whitespace(s): def fix_format(s): - prefix = config.get('flag_prefix') + prefix = config.config.get('flag_prefix') return s if prefix + '{' in s else prefix + '{' + s + '}' diff --git a/src/plugins/points/decay.py b/src/plugins/points/decay.py index bb067f6f..8deb6c62 100644 --- a/src/plugins/points/decay.py +++ b/src/plugins/points/decay.py @@ -6,7 +6,7 @@ from team.models import Team -class DecayPointsPlugin(PointsPlugin): +class DecayPointsPlugin(object): name = "decay" recalculate_type = "custom" diff --git a/src/plugins/tests.py b/src/plugins/tests.py index 76991c14..b3c01ad2 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -205,7 +205,7 @@ def test_score(self): self.assertEquals(self.team.leaderboard_points, 1000) def test_score_lb_disabled(self): - config.set('enable_scoring', False) + config.config.set('enable_scoring', False) self.plugin.score(self.user, self.team, '', Solve.objects.filter(challenge=self.challenge2)) self.assertEquals(self.team.points, 1000) self.assertEquals(self.team.leaderboard_points, 0) diff --git a/src/ractf/management/commands/copy_points.py b/src/ractf/management/commands/copy_points.py index 4230302a..8b019ee0 100644 --- a/src/ractf/management/commands/copy_points.py +++ b/src/ractf/management/commands/copy_points.py @@ -10,7 +10,7 @@ class Command(BaseCommand): help = "Removes all scores from the database" def handle(self, *args, **options): - if time.time() > config.get('end_time'): + if time.time() > config.config.get('end_time'): return for score in Score.objects.all(): if not score.leaderboard: diff --git a/src/stats/apps.py b/src/stats/apps.py index a47a02e6..6a4664eb 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -1,8 +1,9 @@ import sys +from importlib import import_module from django.apps import AppConfig - +import team, member, challenge class StatsConfig(AppConfig): @@ -15,4 +16,11 @@ def ready(self): # Don't run stats-related logic if we haven't migrated yet return - import stats.signals + from . import signals + + Team, Solve, Member = team.models.Team, challenge.models.Solve, member.models.Member + + signals.team_count.set(Team.objects.count()) + signals.solve_count.set(Solve.objects.count()) + signals.member_count.set(Member.objects.count()) + signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) \ No newline at end of file diff --git a/src/stats/signals.py b/src/stats/signals.py index 86c14454..387afaed 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -7,18 +7,10 @@ from member.models import Member from team.models import Team -member_count = Gauge("member_count", "The number of members currently registered") -member_count.set(Member.objects.count()) - team_count = Gauge("team_count", "The number of teams currently registered") -team_count.set(Team.objects.count()) - -solve_count = Gauge("solve_count", "The count of both correct and incorrect solves") -solve_count.set(Solve.objects.count()) - correct_solve_count = Gauge("correct_solve_count", "The count of correct solves") -correct_solve_count.set(Solve.objects.filter(correct=True).count()) - +member_count = Gauge("member_count", "The number of members currently registered") +solve_count = Gauge("solve_count", "The count of both correct and incorrect solves") connected_websocket_users = Gauge( "connected_websocket_users", "The number of users connected to the Websocket" ) diff --git a/src/team/permissions.py b/src/team/permissions.py index de7cd98f..d12ce749 100644 --- a/src/team/permissions.py +++ b/src/team/permissions.py @@ -18,4 +18,4 @@ def has_permission(self, request, view): class TeamsEnabled(permissions.BasePermission): def has_permission(self, request, view): - return config.get("enable_teams") + return config.config.get("enable_teams") diff --git a/src/websockets/signals.py b/src/websockets/signals.py index e28e9429..6eb67330 100644 --- a/src/websockets/signals.py +++ b/src/websockets/signals.py @@ -25,7 +25,7 @@ def broadcast(data): @receiver(flag_score) def on_flag_score(user, team, challenge, flag, solve, **kwargs): - if not config.get('enable_solve_broadcast'): + if not config.config.get('enable_solve_broadcast'): return broadcast({ 'type': 'send_json', From 7bbb18b2ef9ab0fda2ed6fcdcd4fd88ee56cc1f0 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Jun 2021 17:52:16 +0100 Subject: [PATCH 032/185] tests pass pog --- src/backend/exception_handler.py | 3 +- src/challenge/serializers.py | 145 ++++++++++++++++++------------- src/challenge/tests.py | 9 +- src/challenge/views.py | 12 +-- src/leaderboard/tests.py | 17 +++- src/member/models.py | 8 +- src/plugins/points/decay.py | 2 +- src/plugins/providers.py | 2 +- src/stats/tests.py | 3 + 9 files changed, 120 insertions(+), 81 deletions(-) diff --git a/src/backend/exception_handler.py b/src/backend/exception_handler.py index 37607f9e..8c28c3ee 100644 --- a/src/backend/exception_handler.py +++ b/src/backend/exception_handler.py @@ -15,8 +15,7 @@ def handle_exception(exc, context): if "X-Reasonable" in context['request'].headers: return exception_handler(exc, context) - if settings.DEBUG: - traceback.print_exc() + traceback.print_exc() if isinstance(exc, FormattedException): return FormattedResponse(s=False, d=exc.d, m=exc.m, status=exc.status_code) response = exception_handler(exc, context) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index f9dc9e89..c6db9114 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -89,20 +89,6 @@ class FastLockedChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): release_time = DateTimeField() unlock_time_surpassed = serpy.MethodField() - class Meta: - model = Challenge - fields = ['id', 'unlock_requirements', 'challenge_metadata', 'challenge_type', 'hidden', - 'unlock_time_surpassed', 'release_time'] - - -class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - unlock_time_surpassed = serializers.SerializerMethodField() - - class Meta: - model = Challenge - fields = ['id', 'unlock_requirements', 'challenge_metadata', 'challenge_type', 'hidden', - 'unlock_time_surpassed', 'release_time'] - class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): id = serpy.IntField() @@ -129,6 +115,17 @@ def __init__(self, *args, **kwargs): super(FastChallengeSerializer, self).__init__(*args, **kwargs) if 'context' in kwargs: self.context = kwargs['context'] + if 'solve_counter' not in self.context: + self.context.update({ + "request": self.context["request"], + "solves": list( + self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", + flat=True) + ), + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), + }) def serialize(self, instance): if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ @@ -142,49 +139,6 @@ def to_value(self, instance): return self.serialize(instance) -class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - hints = HintSerializer(many=True, read_only=True) - files = FileSerializer(many=True, read_only=True) - solved = serializers.SerializerMethodField() - unlocked = serializers.SerializerMethodField() - unlock_time_surpassed = serializers.SerializerMethodField() - votes = serializers.SerializerMethodField() - first_blood_name = serializers.ReadOnlyField(source='first_blood.username') - solve_count = serializers.SerializerMethodField() - tags = NestedTagSerializer(many=True, read_only=True) - post_score_explanation = serializers.SerializerMethodField() - - class Meta: - model = Challenge - fields = ['id', 'name', 'category', 'description', 'challenge_type', 'challenge_metadata', 'flag_type', - 'author', 'score', 'unlock_requirements', 'hints', 'files', 'solved', 'unlocked', - 'first_blood', 'first_blood_name', 'solve_count', 'hidden', 'votes', 'tags', 'unlock_time_surpassed', - 'post_score_explanation'] - - -class CategorySerializer(serializers.ModelSerializer): - challenges = serializers.SerializerMethodField() - - class Meta: - model = Category - fields = ['id', 'name', 'display_order', 'contained_type', 'description', 'metadata', 'challenges'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['challenges'].context.update({ - "request": self.context["request"], - "solves": list( - self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ), - "solve_counter": get_solve_counts(), - "votes_positive_counter": Counter(ChallengeVote.objects.filter(positive=True).values_list("challenge", flat=True)), - "votes_negative_counter": Counter(ChallengeVote.objects.filter(positive=False).values_list("challenge", flat=True)), - }) - - def get_challenges(self, instance): - return FastChallengeSerializer(instance.challenges, many=True, context=self.context).data - - class FastCategorySerializer(serpy.Serializer): id = serpy.IntField() name = serpy.StrField() @@ -194,10 +148,6 @@ class FastCategorySerializer(serpy.Serializer): metadata = serpy.DictSerializer() challenges = serpy.MethodField() - class Meta: - model = Category - fields = ['id', 'name', 'display_order', 'contained_type', 'description', 'metadata', 'challenges'] - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs: @@ -233,6 +183,52 @@ def create(self, validated_data): return Category.objects.create(**validated_data, display_order=Category.objects.count()) +class FastAdminChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + description = serpy.StrField() + challenge_type = serpy.StrField() + flag_type = serpy.StrField() + author = serpy.StrField() + score = serpy.IntField() + unlock_requirements = serpy.StrField() + hints = FastHintSerializer(many=True) + files = FastFileSerializer(many=True) + solved = serpy.MethodField() + unlocked = serpy.MethodField() + first_blood = ForeignKeyField() + solve_count = serpy.MethodField() + hidden = serpy.BoolField() + votes = serpy.MethodField() + tags = FastNestedTagSerializer(many=True) + unlock_time_surpassed = serpy.MethodField() + post_score_explanation = serpy.StrField() + + def __init__(self, *args, **kwargs): + super(FastAdminChallengeSerializer, self).__init__(*args, **kwargs) + if 'context' in kwargs: + self.context = kwargs['context'] + if 'nested' not in kwargs: + self.context.update({ + "request": self.context["request"], + "solves": list( + self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", + flat=True) + ), + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), + }) + + def serialize(self, instance): + return super(FastAdminChallengeSerializer, FastAdminChallengeSerializer(instance, context=self.context)).to_value(instance) + + def to_value(self, instance): + if self.many: + return [self.serialize(o) for o in instance] + return self.serialize(instance) + + class AdminChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): hints = HintSerializer(many=True, read_only=True) files = FileSerializer(many=True, read_only=True) @@ -286,6 +282,33 @@ class Meta: 'release_time'] +class FastAdminCategorySerializer(serpy.Serializer): + id = serpy.IntField() + name = serpy.StrField() + display_order = serpy.StrField() + contained_type = serpy.StrField() + description = serpy.StrField() + metadata = serpy.DictSerializer() + challenges = serpy.MethodField() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if "context" in kwargs: + self.context = kwargs["context"] + self.context.update({ + "request": self.context["request"], + "solves": list( + self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) + ), + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), + }) + + def get_challenges(self, instance): + return FastAdminChallengeSerializer(instance.challenges, many=True, context=self.context).data + + class AdminScoreSerializer(serializers.ModelSerializer): class Meta: model = Score diff --git a/src/challenge/tests.py b/src/challenge/tests.py index 1e508d0c..f570c710 100644 --- a/src/challenge/tests.py +++ b/src/challenge/tests.py @@ -28,7 +28,7 @@ def setUp(self): challenge1.save() challenge2.save() challenge3.save() - challenge1.unlock_requirements = "2" + challenge1.unlock_requirements = str(challenge2.id) challenge1.save() hint1 = Hint(name='hint1', challenge=challenge1, text='a', penalty=100) hint2 = Hint(name='hint2', challenge=challenge1, text='a', penalty=100) @@ -111,7 +111,7 @@ def test_challenge_unlocks_no_team(self): self.assertFalse(self.challenge1.is_unlocked(user4)) def test_challenge_unlocks_locked(self): - self.assertFalse(self.challenge3.is_unlocked(self.user)) + self.assertFalse(self.challenge1.is_unlocked(self.user)) def test_hint_scoring(self): HintUse(hint=self.hint3, team=self.team, user=self.user, challenge=self.challenge2).save() @@ -288,7 +288,7 @@ def test_challenge_list_authenticated_content(self): def test_challenge_list_challenge_redacting(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('challenges-list')) - self.assertFalse('description' in response.data[0]) + self.assertFalse('description' in response.data[-1]) def test_challenge_list_challenge_redacting_admin(self): self.user.is_staff = True @@ -302,7 +302,8 @@ def test_challenge_list_challenge_unlocked_admin(self): self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse('challenges-list')) - self.assertFalse(response.data[0]['unlocked']) + # TODO: Don't depend on order + self.assertFalse(response.data[-1]['unlocked']) def test_single_challenge_redacting(self): self.user.is_staff = False diff --git a/src/challenge/views.py b/src/challenge/views.py index 54633ac6..ad9dfa54 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -25,10 +25,10 @@ from challenge.models import Challenge, Category, Solve, File, ChallengeVote, ChallengeFeedback, Tag, Score from challenge.permissions import CompetitionOpen from challenge.serializers import ( - ChallengeSerializer, AdminCategorySerializer, - AdminChallengeSerializer, FileSerializer, CreateCategorySerializer, + AdminCategorySerializer, FileSerializer, CreateCategorySerializer, CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, - AdminScoreSerializer, FastCategorySerializer, get_solve_counts, get_positive_votes, get_negative_votes + AdminScoreSerializer, FastCategorySerializer, get_solve_counts, get_positive_votes, get_negative_votes, + FastChallengeSerializer, FastAdminChallengeSerializer, FastAdminCategorySerializer ) import config from hint.models import Hint, HintUse @@ -50,7 +50,7 @@ class CategoryViewset(AdminCreateModelViewSet): throttle_scope = 'challenges' pagination_class = None serializer_class = FastCategorySerializer - admin_serializer_class = AdminCategorySerializer + admin_serializer_class = FastAdminCategorySerializer create_serializer_class = CreateCategorySerializer def get_queryset(self): @@ -126,8 +126,8 @@ class ChallengeViewset(AdminCreateModelViewSet): permission_classes = (CompetitionOpen & AdminOrReadOnly,) throttle_scope = 'challenges' pagination_class = None - serializer_class = ChallengeSerializer - admin_serializer_class = AdminChallengeSerializer + serializer_class = FastChallengeSerializer + admin_serializer_class = FastAdminChallengeSerializer create_serializer_class = CreateChallengeSerializer def get_queryset(self): diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 97189aae..2742f8d3 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -41,48 +41,61 @@ def setUp(self): self.user = user def test_unauthed_access(self): + config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) + config.config.set('enable_caching', True) self.assertEquals(response.status_code, HTTP_200_OK) def test_authed_access(self): self.client.force_authenticate(self.user) + config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) + config.config.set('enable_caching', True) self.assertEquals(response.status_code, HTTP_200_OK) def test_disabled_access(self): + config.config.set('enable_caching', False) config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-graph')) config.config.set("enable_scoreboard", True) + config.config.set('enable_caching', True) self.assertEquals(response.data["d"], {}) def test_format(self): + config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) + config.config.set('enable_caching', True) self.assertTrue('user' in response.data['d']) self.assertTrue('team' in response.data['d']) def test_list_size(self): + config.config.set('enable_caching', False) populate() response = self.client.get(reverse('leaderboard-graph')) + config.config.set('enable_caching', True) self.assertEquals(len(response.data['d']['user']), 10) self.assertEquals(len(response.data['d']['team']), 10) def test_list_sorting(self): + config.config.set('enable_caching', False) populate() response = self.client.get(reverse('leaderboard-graph')) + config.config.set('enable_caching', True) self.assertEquals(response.data['d']['user'][0]['points'], 1400) self.assertEquals(response.data['d']['team'][0]['points'], 1400) def test_user_only(self): populate() config.config.set("enable_teams", False) + config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) config.config.set("enable_teams", True) + config.config.set('enable_caching', True) self.assertEquals(len(response.data['d']['user']), 10) self.assertEquals(response.data['d']['user'][0]['points'], 1400) self.assertNotIn("team", response.data['d'].keys()) - class UserListTestCase(APITestCase): def setUp(self): @@ -108,9 +121,7 @@ def test_disabled_access(self): def test_length(self): populate() - print(Score.objects.all()) response = self.client.get(reverse('leaderboard-user')) - print(response.content) self.assertEquals(len(response.data['d']['results']), 15) def test_order(self): diff --git a/src/member/models.py b/src/member/models.py index 10c703dd..96ae650e 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -55,9 +55,10 @@ def __str__(self): return self.username def can_login(self): + from config import config return self.is_staff or ( - config.config.get("enable_login") - and (config.config.get("enable_prelogin") or config.config.get("start_time") <= time.time()) + config.get("enable_login") + and (config.get("enable_prelogin") or config.get("start_time") <= time.time()) ) def issue_token(self, owner=None): @@ -71,7 +72,8 @@ def has_2fa(self): return hasattr(self, "totp_device") and self.totp_device.verified def should_deny_admin(self): - return config.config.get("enable_force_admin_2fa") and not self.has_2fa() + from config import config + return config.get("enable_force_admin_2fa") and not self.has_2fa() class UserIP(ExportModelOperationsMixin("user_ip"), models.Model): diff --git a/src/plugins/points/decay.py b/src/plugins/points/decay.py index 8deb6c62..bb067f6f 100644 --- a/src/plugins/points/decay.py +++ b/src/plugins/points/decay.py @@ -6,7 +6,7 @@ from team.models import Team -class DecayPointsPlugin(object): +class DecayPointsPlugin(PointsPlugin): name = "decay" recalculate_type = "custom" diff --git a/src/plugins/providers.py b/src/plugins/providers.py index 95b70d1a..822f2761 100644 --- a/src/plugins/providers.py +++ b/src/plugins/providers.py @@ -11,7 +11,7 @@ def register_provider(provider_type, provider): def get_provider(provider_type): - return providers[provider_type][config.get(provider_type + '_provider')] + return providers[provider_type][config.config.get(provider_type + '_provider')] class Provider(abc.ABC): diff --git a/src/stats/tests.py b/src/stats/tests.py index da1f91a3..f017e6ca 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -3,6 +3,7 @@ from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED from rest_framework.test import APITestCase +import config from challenge.models import Challenge, Category, Solve from team.models import Team @@ -104,7 +105,9 @@ def test_challenge_data(self): Solve.objects.create(challenge=chall, flag="", correct=False) self.client.force_authenticate(user) + config.config.set('enable_caching', False) response = self.client.get(reverse("full")) + config.config.set('enable_caching', True) self.assertEquals(response.data["d"]["challenges"][chall.id]["incorrect"], 1) self.assertEquals(response.data["d"]["challenges"][chall.id]["correct"], 1) From 8b0d6444185d41116df60da292f8f0b64a9748a7 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Jun 2021 17:56:01 +0100 Subject: [PATCH 033/185] thank you pycharm --- src/member/tests.py | 1 - src/team/tests.py | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/member/tests.py b/src/member/tests.py index 7201ed21..e32fe9d0 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -33,7 +33,6 @@ def test_self_change_email(self): response = self.client.put( reverse("member-self"), data={"email": "test-self2@example.org", "username": "test-self"} ) - print(response.data) self.assertEquals(response.status_code, HTTP_200_OK) self.assertEquals(get_user_model().objects.get(id=self.user.id).email, "test-self2@example.org") diff --git a/src/team/tests.py b/src/team/tests.py index 318723f1..69054d53 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -66,7 +66,6 @@ def test_update_not_owner(self): self.admin_user.is_staff = False self.admin_user.save() self.client.force_authenticate(user=self.admin_user) - print(self.team.owner == self.admin_user) response = self.client.patch(reverse("team-self"), data={"name": "name-change"}) self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) @@ -231,8 +230,6 @@ def test_visible_admin(self): self.team.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("team-list")) - print(response.data) - print(self.user.should_deny_admin()) self.assertEquals(len(response.data['d']["results"]), 1) def test_visible_not_admin(self): @@ -240,7 +237,7 @@ def test_visible_not_admin(self): self.team.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("team-list")) - print(response.data) + self.assertEquals(len(response.data["d"]["results"]), 0) def test_visible_detail_admin(self): From 8f2c127a0469a05dbd6229df3ff72cca59dfa232 Mon Sep 17 00:00:00 2001 From: Daniel Milnes Date: Wed, 2 Jun 2021 18:06:06 +0100 Subject: [PATCH 034/185] Disable Poetry preview --- .github/workflows/docs.yml | 2 -- .github/workflows/test.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 340d65c9..92e5b994 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -79,8 +79,6 @@ jobs: - name: Install Poetry run: curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - env: - POETRY_PREVIEW: 1 - name: Disable Virtualenvs run: $HOME/.poetry/bin/poetry config virtualenvs.create false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c6c863f..e7294b90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,8 +64,6 @@ jobs: - name: Install Poetry run: curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - env: - POETRY_PREVIEW: 1 - name: Disable Virtualenvs run: $HOME/.poetry/bin/poetry config virtualenvs.create false From 0300ed3f2fc3030fa0907c6abc96047013ca97a4 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 12:50:37 +0100 Subject: [PATCH 035/185] Add config LazyLoader --- src/config/__init__.py | 6 ++++++ src/plugins/providers.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/config/__init__.py b/src/config/__init__.py index e69de29b..0883d7ad 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -0,0 +1,6 @@ +from importlib import import_module + +from django.utils.functional import SimpleLazyObject + + +config = SimpleLazyObject(lambda: import_module("config.config", "config")) diff --git a/src/plugins/providers.py b/src/plugins/providers.py index 822f2761..ce225027 100644 --- a/src/plugins/providers.py +++ b/src/plugins/providers.py @@ -11,7 +11,7 @@ def register_provider(provider_type, provider): def get_provider(provider_type): - return providers[provider_type][config.config.get(provider_type + '_provider')] + return providers[provider_type][config.config.get(provider_type + "_provider")] class Provider(abc.ABC): From 6734ace29d96958eea51e720944115a72396a877 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 12:53:27 +0100 Subject: [PATCH 036/185] Remove deprecated 'assertEquals' calls --- src/authentication/tests.py | 130 ++++++++++++++++----------------- src/challenge/tests.py | 58 +++++++-------- src/config/tests.py | 14 ++-- src/experiments/tests.py | 2 +- src/hint/tests.py | 28 +++---- src/leaderboard/tests.py | 64 ++++++++-------- src/member/tests.py | 28 +++---- src/plugins/tests.py | 20 ++--- src/scorerecalculator/tests.py | 34 ++++----- src/stats/tests.py | 34 ++++----- src/team/tests.py | 62 ++++++++-------- 11 files changed, 237 insertions(+), 237 deletions(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index db1df418..7fe67422 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -31,7 +31,7 @@ def test_register(self): 'email': 'user@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_with_mail(self): with self.settings(MAIL={ @@ -46,7 +46,7 @@ def test_register_with_mail(self): 'email': 'user@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_weak_password(self): data = { @@ -55,7 +55,7 @@ def test_register_weak_password(self): 'email': 'user2@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_duplicate_username(self): data = { @@ -64,14 +64,14 @@ def test_register_duplicate_username(self): 'email': 'user3@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) data = { 'username': 'user3', 'password': 'uO7*$E@0ngqL', 'email': 'user4@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_duplicate_email(self): data = { @@ -80,14 +80,14 @@ def test_register_duplicate_email(self): 'email': 'user4@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) data = { 'username': 'user5', 'password': 'uO7*$E@0ngqL', 'email': 'user4@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) @mock.patch('time.time', side_effect=get_fake_time) def test_register_closed(self, mock_obj): @@ -98,7 +98,7 @@ def test_register_closed(self, mock_obj): 'email': 'user6@example.org', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) config.config.set('enable_prelogin', True) def test_register_admin(self): @@ -130,7 +130,7 @@ def test_register_malformed(self): 'username': 'user6', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_invalid_email(self): data = { @@ -139,7 +139,7 @@ def test_register_invalid_email(self): 'email': 'user6', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_teams_disabled(self): config.config.set('enable_teams', False) @@ -150,8 +150,8 @@ def test_register_teams_disabled(self): } response = self.client.post(reverse('register'), data) config.config.set('enable_teams', True) - self.assertEquals(response.status_code, HTTP_201_CREATED) - self.assertEquals(get_user_model().objects.get(username='user10').team.name, 'user10') + self.assertEqual(response.status_code, HTTP_201_CREATED) + self.assertEqual(get_user_model().objects.get(username='user10').team.name, 'user10') class EmailResendTestCase(APITestCase): @@ -160,19 +160,19 @@ def test_email_resend(self): user = get_user_model()(username="test_verify_user", email_verified=False, email="tvu@example.com") user.save() response = self.client.post(reverse('resend-email'), {"email": "tvu@example.com"}) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_already_verified_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): user = get_user_model()(username="resend-email", email_verified=True, email="tvu@example.com") user.save() response = self.client.post(reverse('resend-email'), {"email": "tvu@example.com"}) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_non_existing_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): response = self.client.post(reverse('resend-email'), {"email": "nonexisting@example.com"}) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) class SudoTestCase(APITestCase): @@ -184,7 +184,7 @@ def test_sudo(self): self.client.force_authenticate(user) req = self.client.post(reverse('sudo'), {"id": user2.id}) - self.assertEquals(req.status_code, HTTP_200_OK) + self.assertEqual(req.status_code, HTTP_200_OK) class GenerateInvitesTestCase(APITestCase): @@ -194,7 +194,7 @@ def test_response_length(self): self.client.force_authenticate(user=user) team = Team.objects.create(owner=user, name=user.username, password='123123') response = self.client.post(reverse('generate-invites'), {"amount": 15, "auto_team": team.id, "max_uses": 1}) - self.assertEquals(len(response.data["d"]["invite_codes"]), 15) + self.assertEqual(len(response.data["d"]["invite_codes"]), 15) def test_invites_viewset(self): user = get_user_model()(username="resend-email", is_staff=True, email="tvu@example.com", is_superuser=True) @@ -202,7 +202,7 @@ def test_invites_viewset(self): self.client.force_authenticate(user=user) self.client.post(reverse('generate-invites'), {"amount": 15, "max_uses": 1}) response = self.client.get(reverse('invites-list')) - self.assertEquals(len(response.data["d"]["results"]), 15) + self.assertEqual(len(response.data["d"]["results"]), 15) @@ -234,7 +234,7 @@ def test_register_invite_required_missing_invite(self): 'email': 'user7@example.com', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_invite_required_valid(self): data = { @@ -244,7 +244,7 @@ def test_register_invite_required_valid(self): 'invite': 'test1', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_invite_required_invalid(self): data = { @@ -254,7 +254,7 @@ def test_register_invite_required_invalid(self): 'invite': 'test1---', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_register_invite_required_already_used(self): data = { @@ -271,7 +271,7 @@ def test_register_invite_required_already_used(self): 'invite': 'test2', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_register_invite_required_valid_maxing_uses(self): data = { @@ -281,7 +281,7 @@ def test_register_invite_required_valid_maxing_uses(self): 'invite': 'test3', } response = self.client.post(reverse('register'), data) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_invite_required_auto_team(self): data = { @@ -291,7 +291,7 @@ def test_register_invite_required_auto_team(self): 'invite': 'test4', } response = self.client.post(reverse('register'), data) - self.assertEquals(get_user_model().objects.get(username='user12').team.id, self.team.id) + self.assertEqual(get_user_model().objects.get(username='user12').team.id, self.team.id) class LogoutTestCase(APITestCase): @@ -307,11 +307,11 @@ def test_logout(self): self.client.post(reverse('login'), data={'username': self.user.username, 'password': 'password', 'otp': ''}) self.client.force_authenticate(user=self.user) response = self.client.post(reverse('logout')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_logout_not_logged_in(self): response = self.client.post(reverse('logout')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class LoginTestCase(APITestCase): @@ -330,7 +330,7 @@ def test_login(self): 'password': 'password', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_login_invalid(self): data = { @@ -338,14 +338,14 @@ def test_login_invalid(self): 'password': 'a', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_missing_data(self): data = { 'username': 'login-test', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_email_not_verified(self): self.user.email_verified = False @@ -355,7 +355,7 @@ def test_login_email_not_verified(self): 'password': 'password', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) @mock.patch('time.time', side_effect=get_fake_time) def test_login_login_closed(self, mock_obj): @@ -366,7 +366,7 @@ def test_login_login_closed(self, mock_obj): config.config.set('enable_prelogin', False) response = self.client.post(reverse('login'), data) config.config.set('enable_prelogin', True) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_inactive(self): self.user.is_active = False @@ -376,7 +376,7 @@ def test_login_inactive(self): 'password': 'password', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) self.user.is_active = True self.user.save() @@ -387,7 +387,7 @@ def test_login_with_email(self): 'otp': '', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_login_wrong_user(self): data = { @@ -396,7 +396,7 @@ def test_login_wrong_user(self): 'otp': '', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_wrong_password(self): data = { @@ -405,7 +405,7 @@ def test_login_wrong_password(self): 'otp': '', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_malformed(self): data = { @@ -413,7 +413,7 @@ def test_login_malformed(self): 'otp': '', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_2fa_required(self): TOTPDevice(user=self.user, verified=True).save() @@ -422,7 +422,7 @@ def test_login_2fa_required(self): 'password': 'password', } response = self.client.post(reverse('login'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class Login2FATestCase(APITestCase): @@ -445,7 +445,7 @@ def test_login_2fa(self): 'tfa': totp.now(), } response = self.client.post(reverse('login-2fa'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_login_2fa_invalid(self): data = { @@ -454,7 +454,7 @@ def test_login_2fa_invalid(self): 'tfa': '123456', } response = self.client.post(reverse('login-2fa'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_2fa_without_2fa(self): user = get_user_model()(username='login-test-no-2fa', email='login-test-no-2fa@example.org') @@ -467,7 +467,7 @@ def test_login_2fa_without_2fa(self): 'tfa': '123456' } response = self.client.post(reverse('login-2fa'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_2fa_missing(self): data = { @@ -475,7 +475,7 @@ def test_login_2fa_missing(self): 'password': 'password', } response = self.client.post(reverse('login-2fa'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_2fa_backup_cde(self): BackupCode(user=self.user, code='12345678').save() @@ -485,7 +485,7 @@ def test_login_2fa_backup_cde(self): 'tfa': '12345678', } response = self.client.post(reverse('login-2fa'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) class TokenTestCase(APITestCase): @@ -493,7 +493,7 @@ def test_token_str(self): user = get_user_model()(username='token-test', email='token-test@example.org') user.save() tok = Token(key="a"*40, user=user) - self.assertEquals(str(tok), "a"*40) + self.assertEqual(str(tok), "a"*40) class TFATestCase(APITestCase): @@ -509,7 +509,7 @@ def setUp(self): def test_add_2fa_unauthenticated(self): response = self.client.post(reverse('add-2fa')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) self.assertFalse(self.user.has_2fa()) def test_add_2fa(self): @@ -522,7 +522,7 @@ def test_add_2fa_twice(self): self.client.force_authenticate(user=self.user) self.client.post(reverse('add-2fa')) response = self.client.post(reverse('add-2fa')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_verify_2fa(self): self.client.force_authenticate(user=self.user) @@ -545,7 +545,7 @@ def test_add_2fa_with_2fa(self): totp = pyotp.TOTP(secret) self.client.post(reverse('verify-2fa'), data={'otp': totp.now()}) response = self.client.post(reverse('add-2fa')) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_remove_2fa(self): self.client.force_authenticate(user=self.user) @@ -557,7 +557,7 @@ def test_remove_2fa(self): response = self.client.post(reverse('remove-2fa'), data={ 'otp': pyotp.TOTP(totp_device.totp_secret).now() }) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_remove_2fa_fail(self): self.client.force_authenticate(user=self.user) @@ -569,7 +569,7 @@ def test_remove_2fa_fail(self): response = self.client.post(reverse('remove-2fa'), data={ 'otp': "invalid_otp" }) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_remove_2fa_removes_2fa(self): self.client.force_authenticate(user=self.user) @@ -590,7 +590,7 @@ def test_remove_2fa_no_2fa(self): user.totp_device = None user.save() response = self.client.post(reverse('remove-2fa')) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) class RequestPasswordResetTestCase(APITestCase): @@ -600,7 +600,7 @@ def setUp(self): def test_password_reset_request_invalid(self): response = self.client.post(reverse('request-password-reset'), data={'email': 'user10@example.org'}) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_password_reset_request_valid(self): with self.settings(MAIL={ @@ -611,7 +611,7 @@ def test_password_reset_request_valid(self): }): get_user_model()(username='test-password-rest', email='user10@example.org', email_verified=True).save() response = self.client.post(reverse('request-password-reset'), data={'email': 'user10@example.org'}) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) class DoPasswordResetTestCase(APITestCase): @@ -632,7 +632,7 @@ def test_password_reset(self): 'password': 'uO7*$E@0ngqL', } response = self.client.post(reverse('do-password-reset'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_password_reset_issues_token(self): data = { @@ -650,7 +650,7 @@ def test_password_reset_bad_token(self): 'password': 'uO7*$E@0ngqL', } response = self.client.post(reverse('do-password-reset'), data) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_password_reset_weak_password(self): data = { @@ -659,7 +659,7 @@ def test_password_reset_weak_password(self): 'password': 'password', } response = self.client.post(reverse('do-password-reset'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_password_reset_login_disabled(self): config.config.set('enable_login', False) @@ -700,7 +700,7 @@ def test_email_verify(self): 'token': self.user.email_token, } response = self.client.post(reverse('verify-email'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_email_verify_invalid(self): data = { @@ -708,7 +708,7 @@ def test_email_verify_invalid(self): 'token': 'haha brr', } response = self.client.post(reverse('verify-email'), data) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_email_verify_nologin(self): config.config.set("enable_login", False) @@ -719,7 +719,7 @@ def test_email_verify_nologin(self): } response = self.client.post(reverse('verify-email'), data) config.config.set("enable_login", False) - self.assertEquals(response.data["d"], "") + self.assertEqual(response.data["d"], "") def test_email_verify_twice(self): data = { @@ -728,7 +728,7 @@ def test_email_verify_twice(self): } response = self.client.post(reverse('verify-email'), data) response = self.client.post(reverse('verify-email'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_email_verify_bad_token(self): data = { @@ -736,7 +736,7 @@ def test_email_verify_bad_token(self): 'token': 'abc', } response = self.client.post(reverse('verify-email'), data) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) class ChangePasswordTestCase(APITestCase): @@ -756,7 +756,7 @@ def test_change_password(self): } response = self.client.post(reverse('change-password'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_change_password_weak(self): self.client.force_authenticate(user=self.user) @@ -765,7 +765,7 @@ def test_change_password_weak(self): 'password': 'password', } response = self.client.post(reverse('change-password'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_change_password_invalid_old(self): self.client.force_authenticate(user=self.user) @@ -774,7 +774,7 @@ def test_change_password_invalid_old(self): 'password': 'password', } response = self.client.post(reverse('change-password'), data) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class RegerateBackupCodesTestCase(APITestCase): @@ -790,12 +790,12 @@ def setUp(self): def test_regenerate_backup_codes_count(self): self.client.force_authenticate(user=self.user) response = self.client.post(reverse('regenerate-backup-codes')) - self.assertEquals(len(response.data['d']['backup_codes']), 10) + self.assertEqual(len(response.data['d']['backup_codes']), 10) def test_regenerate_backup_codes_length(self): self.client.force_authenticate(user=self.user) response = self.client.post(reverse('regenerate-backup-codes')) - self.assertEquals(sum([len(x) for x in response.data['d']['backup_codes']]), 80) + self.assertEqual(sum([len(x) for x in response.data['d']['backup_codes']]), 80) def test_regenerate_backup_codes_unique(self): self.client.force_authenticate(user=self.user) @@ -809,7 +809,7 @@ def test_regenerate_backup_codes_no_2fa(self): user.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) response = self.client.post(reverse('regenerate-backup-codes')) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) class CreateBotUserTestCase(APITestCase): diff --git a/src/challenge/tests.py b/src/challenge/tests.py index f570c710..b4a31de5 100644 --- a/src/challenge/tests.py +++ b/src/challenge/tests.py @@ -77,8 +77,8 @@ def solve_challenge(self): def test_challenge_solve(self): response = self.solve_challenge() - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(response.data['d']['correct'], True) + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data['d']['correct'], True) def test_challenge_solve_incorrect_flag(self): self.client.force_authenticate(user=self.user) @@ -87,8 +87,8 @@ def test_challenge_solve_incorrect_flag(self): 'challenge': self.challenge2.id, } response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(response.data['d']['correct'], False) + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data['d']['correct'], False) def test_challenge_double_solve(self): self.solve_challenge() @@ -98,7 +98,7 @@ def test_challenge_double_solve(self): 'challenge': self.challenge2.id, } response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_challenge_unlocks(self): self.solve_challenge() @@ -117,27 +117,27 @@ def test_hint_scoring(self): HintUse(hint=self.hint3, team=self.team, user=self.user, challenge=self.challenge2).save() self.solve_challenge() response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['points'], 900) + self.assertEqual(response.data['solves'][0]['points'], 900) def test_solve_first_blood(self): self.solve_challenge() response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['first_blood'], True) + self.assertEqual(response.data['solves'][0]['first_blood'], True) def test_solve_solved_by_name(self): self.solve_challenge() response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['solved_by_name'], 'challenge-test') + self.assertEqual(response.data['solves'][0]['solved_by_name'], 'challenge-test') def test_solve_team_name(self): self.solve_challenge() response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['team_name'], 'team') + self.assertEqual(response.data['solves'][0]['team_name'], 'team') def test_normal_scoring(self): self.solve_challenge() response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['points'], 1000) + self.assertEqual(response.data['solves'][0]['points'], 1000) def test_is_solved(self): self.solve_challenge() @@ -150,7 +150,7 @@ def test_submission_disabled(self): config.config.set('enable_flag_submission', False) response = self.solve_challenge() config.config.set('enable_flag_submission', True) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_submission_malformed(self): self.client.force_authenticate(user=self.user) @@ -158,7 +158,7 @@ def test_submission_malformed(self): 'flag': 'ractf{a}', } response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_challenge_score_same_team(self): self.client.force_authenticate(user=self.user) @@ -173,7 +173,7 @@ def test_challenge_score_same_team(self): 'challenge': self.challenge2.id, } response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_challenge_score_not_first_blood(self): self.solve_challenge() @@ -183,7 +183,7 @@ def test_challenge_score_not_first_blood(self): 'challenge': self.challenge2.id, } response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) self.assertFalse(Solve.objects.get(team=self.team2, challenge=self.challenge2).first_blood) def test_challenge_solved_unauthed(self): @@ -202,24 +202,24 @@ class CategoryViewsetTestCase(ChallengeSetupMixin, APITestCase): def test_category_list_unauthenticated_permission(self): response = self.client.get(reverse('categories-list')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_category_list_authenticated_permission(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('categories-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_category_list_unauthenticated_content(self): response = self.client.get(reverse('categories-list')) self.assertFalse(response.data['s']) - self.assertEquals(response.data['m'], 'not_authenticated') - self.assertEquals(response.data['d'], '') + self.assertEqual(response.data['m'], 'not_authenticated') + self.assertEqual(response.data['d'], '') def test_category_list_authenticated_content(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('categories-list')) - self.assertEquals(len(response.data['d']), 1) - self.assertEquals(len(response.data['d'][0]['challenges']), 3) + self.assertEqual(len(response.data['d']), 1) + self.assertEqual(len(response.data['d'][0]['challenges']), 3) def test_category_list_challenge_redacting(self): self.client.force_authenticate(self.user) @@ -267,23 +267,23 @@ class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): def test_challenge_list_unauthenticated_permission(self): response = self.client.get(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_challenge_list_authenticated_permission(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_challenge_list_unauthenticated_content(self): response = self.client.get(reverse('challenges-list')) self.assertFalse(response.data['s']) - self.assertEquals(response.data['m'], 'not_authenticated') - self.assertEquals(response.data['d'], '') + self.assertEqual(response.data['m'], 'not_authenticated') + self.assertEqual(response.data['d'], '') def test_challenge_list_authenticated_content(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('challenges-list')) - self.assertEquals(len(response.data), 3) + self.assertEqual(len(response.data), 3) def test_challenge_list_challenge_redacting(self): self.client.force_authenticate(self.user) @@ -331,14 +331,14 @@ def test_user_post_detail(self): self.user.save() self.client.force_authenticate(self.user) response = self.client.post(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_user_post_list(self): self.user.is_staff = False self.user.save() self.client.force_authenticate(self.user) response = self.client.post(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create_challenge(self): self.user.is_staff = True @@ -350,7 +350,7 @@ def test_create_challenge(self): 'author': 'dave', 'score': 1000, 'unlock_requirements': "", 'flag_metadata': {}, 'tags': [], }, format='json') - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_create_challenge_unauthorized(self): self.user.is_staff = False @@ -361,5 +361,5 @@ def test_create_challenge_unauthorized(self): 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', 'author': 'dave', 'score': 1000, 'unlock_requirements': "a", 'flag_metadata': {} }, format='json') - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) diff --git a/src/config/tests.py b/src/config/tests.py index bf1cfd40..3e1eb663 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -19,34 +19,34 @@ def setUp(self): def test_auth_unauthed(self): response = self.client.get(reverse('config-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_auth_authed(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('config-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_auth_authed_staff(self): self.client.force_authenticate(self.staff_user) response = self.client.get(reverse('config-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_post_authed(self): self.client.force_authenticate(self.user) response = self.client.post(reverse('config-pk', kwargs={'name': 'test'}), data={'key': 'test', 'value': 'test'}, format='json') - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create(self): self.client.force_authenticate(self.staff_user) response = self.client.post(reverse('config-pk', kwargs={'name': 'test'}), data={'value': 'test'}, format='json') - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_update(self): self.client.force_authenticate(self.staff_user) self.client.post(reverse('config-pk', kwargs={'name': 'test'}), data={'value': 'test'}, format='json') response = self.client.patch(reverse('config-pk', kwargs={'name': 'test'}), data={'value': 'test2'}, format='json') - self.assertEquals(response.status_code, HTTP_204_NO_CONTENT) - self.assertEquals(config.config.get('test'), 'test2') + self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) + self.assertEqual(config.config.get('test'), 'test2') diff --git a/src/experiments/tests.py b/src/experiments/tests.py index e3c4b0cd..52278c0c 100644 --- a/src/experiments/tests.py +++ b/src/experiments/tests.py @@ -6,4 +6,4 @@ class ExperimentsTestCase(APITestCase): def test_experiments(self): with self.settings(EXPERIMENT_OVERRIDES={"test": True}): response = self.client.get(reverse("experiments")) - self.assertEquals(response.data["d"]["test"], True) + self.assertEqual(response.data["d"]["test"], True) diff --git a/src/hint/tests.py b/src/hint/tests.py index 34028e8c..a06c55ea 100644 --- a/src/hint/tests.py +++ b/src/hint/tests.py @@ -16,31 +16,31 @@ def setUp(self): def test_hint_view(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("hint-detail", kwargs={"pk": self.hint1.id})) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_hint_view_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("hint-detail", kwargs={"pk": self.hint1.id})) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_hint_list(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("hint-list")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_hint_list_redaction(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("hint-list")) - self.assertEquals(response.data[0]["text"], "") + self.assertEqual(response.data[0]["text"], "") def test_hint_list_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("hint-list")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_hint_list_redaction_admin(self): self.user.is_staff = True @@ -55,7 +55,7 @@ def test_hint_post(self): reverse("hint-list"), data={"name": "test-hint", "penalty": 100, "challenge": self.challenge2.id}, ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_hint_detail_put(self): self.client.force_authenticate(self.user) @@ -63,7 +63,7 @@ def test_hint_detail_put(self): reverse("hint-detail", kwargs={"pk": self.hint1.id}), data={"name": "test-hint"}, ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_hint_post_admin(self): self.user.is_staff = True @@ -78,7 +78,7 @@ def test_hint_post_admin(self): "text": "abc", }, ) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_hint_detail_patch_admin(self): self.user.is_staff = True @@ -88,7 +88,7 @@ def test_hint_detail_patch_admin(self): reverse("hint-detail", kwargs={"pk": self.hint3.id}), data={"name": "test-hint"}, ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_hint_detail_patch(self): self.client.force_authenticate(self.user) @@ -96,27 +96,27 @@ def test_hint_detail_patch(self): reverse("hint-detail", kwargs={"pk": self.hint3.id}), data={"name": "test-hint"}, ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_hint_use(self): self.client.force_authenticate(self.user) response = self.client.post(reverse("hint-use"), data={"id": self.hint3.id}) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_hint_use_read(self): self.client.force_authenticate(self.user) self.client.post(reverse("hint-use"), data={"id": self.hint3.id}) response = self.client.get(reverse("hint-detail", kwargs={"pk": self.hint3.id})) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) self.assertNotEquals(response.data["text"], "") def test_hint_use_duplicate(self): self.client.force_authenticate(self.user) self.client.post(reverse("hint-use"), data={"id": self.hint3.id}) response = self.client.post(reverse("hint-use"), data={"id": self.hint3.id}) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_hint_use_locked(self): self.client.force_authenticate(self.user) response = self.client.post(reverse("hint-use"), data={"id": self.hint1.id}) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 2742f8d3..3b3ee332 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -44,14 +44,14 @@ def test_unauthed_access(self): config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) config.config.set('enable_caching', True) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_access(self): self.client.force_authenticate(self.user) config.config.set('enable_caching', False) response = self.client.get(reverse('leaderboard-graph')) config.config.set('enable_caching', True) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set('enable_caching', False) @@ -59,7 +59,7 @@ def test_disabled_access(self): response = self.client.get(reverse('leaderboard-graph')) config.config.set("enable_scoreboard", True) config.config.set('enable_caching', True) - self.assertEquals(response.data["d"], {}) + self.assertEqual(response.data["d"], {}) def test_format(self): config.config.set('enable_caching', False) @@ -73,16 +73,16 @@ def test_list_size(self): populate() response = self.client.get(reverse('leaderboard-graph')) config.config.set('enable_caching', True) - self.assertEquals(len(response.data['d']['user']), 10) - self.assertEquals(len(response.data['d']['team']), 10) + self.assertEqual(len(response.data['d']['user']), 10) + self.assertEqual(len(response.data['d']['team']), 10) def test_list_sorting(self): config.config.set('enable_caching', False) populate() response = self.client.get(reverse('leaderboard-graph')) config.config.set('enable_caching', True) - self.assertEquals(response.data['d']['user'][0]['points'], 1400) - self.assertEquals(response.data['d']['team'][0]['points'], 1400) + self.assertEqual(response.data['d']['user'][0]['points'], 1400) + self.assertEqual(response.data['d']['team'][0]['points'], 1400) def test_user_only(self): populate() @@ -91,8 +91,8 @@ def test_user_only(self): response = self.client.get(reverse('leaderboard-graph')) config.config.set("enable_teams", True) config.config.set('enable_caching', True) - self.assertEquals(len(response.data['d']['user']), 10) - self.assertEquals(response.data['d']['user'][0]['points'], 1400) + self.assertEqual(len(response.data['d']['user']), 10) + self.assertEqual(response.data['d']['user'][0]['points'], 1400) self.assertNotIn("team", response.data['d'].keys()) @@ -106,29 +106,29 @@ def setUp(self): def test_unauthed(self): response = self.client.get(reverse('leaderboard-user')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-user')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-user')) config.config.set("enable_scoreboard", True) - self.assertEquals(response.data["d"], {}) + self.assertEqual(response.data["d"], {}) def test_length(self): populate() response = self.client.get(reverse('leaderboard-user')) - self.assertEquals(len(response.data['d']['results']), 15) + self.assertEqual(len(response.data['d']['results']), 15) def test_order(self): populate() response = self.client.get(reverse('leaderboard-user')) points = [x['leaderboard_points'] for x in response.data['d']['results']] - self.assertEquals(points, sorted(points, reverse=True)) + self.assertEqual(points, sorted(points, reverse=True)) class TeamListTestCase(APITestCase): @@ -141,29 +141,29 @@ def setUp(self): def test_unauthed(self): response = self.client.get(reverse('leaderboard-team')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-team')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-team')) config.config.set("enable_scoreboard", True) - self.assertEquals(response.data["d"], {}) + self.assertEqual(response.data["d"], {}) def test_length(self): populate() response = self.client.get(reverse('leaderboard-team')) - self.assertEquals(len(response.data["d"]["results"]), 15) + self.assertEqual(len(response.data["d"]["results"]), 15) def test_order(self): populate() response = self.client.get(reverse('leaderboard-team')) points = [x['leaderboard_points'] for x in response.data['d']['results']] - self.assertEquals(points, sorted(points, reverse=True)) + self.assertEqual(points, sorted(points, reverse=True)) class CTFTimeListTestCase(APITestCase): @@ -176,35 +176,35 @@ def setUp(self): def test_unauthed(self): response = self.client.get(reverse('leaderboard-ctftime')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-ctftime')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-ctftime')) config.config.set("enable_scoreboard", True) - self.assertEquals(response.data, {}) + self.assertEqual(response.data, {}) def test_disabled_ctftime(self): config.config.set("enable_ctftime", False) response = self.client.get(reverse('leaderboard-ctftime')) config.config.set("enable_ctftime", True) - self.assertEquals(response.data, {}) + self.assertEqual(response.data, {}) def test_length(self): populate() response = self.client.get(reverse('leaderboard-ctftime')) - self.assertEquals(len(response.data["standings"]), 15) + self.assertEqual(len(response.data["standings"]), 15) def test_order(self): populate() response = self.client.get(reverse('leaderboard-ctftime')) points = [x['score'] for x in response.data['standings']] - self.assertEquals(points, sorted(points, reverse=True)) + self.assertEqual(points, sorted(points, reverse=True)) class MatrixTestCase(APITestCase): @@ -219,35 +219,35 @@ def setUp(self): def test_authenticated(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_unauthenticated(self): response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_length(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEquals(len(response.data['d']['results']), 15) + self.assertEqual(len(response.data['d']['results']), 15) def test_solves_present(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEquals(len(response.data['d']['results'][0]['solve_ids']), 1) + self.assertEqual(len(response.data['d']['results'][0]['solve_ids']), 1) def test_solves_not_present(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEquals(len(response.data['d']['results'][1]['solve_ids']), 0) + self.assertEqual(len(response.data['d']['results'][1]['solve_ids']), 0) def test_order(self): self.client.force_authenticate(self.user) response = self.client.get(reverse('leaderboard-matrix-list')) points = [x['leaderboard_points'] for x in response.data['d']['results']] - self.assertEquals(points, sorted(points, reverse=True)) + self.assertEqual(points, sorted(points, reverse=True)) def test_disabled_scoreboard(self): config.config.set("enable_scoreboard", False) response = self.client.get(reverse('leaderboard-matrix-list')) config.config.set("enable_scoreboard", True) - self.assertEquals(response.data['d'], {}) + self.assertEqual(response.data['d'], {}) diff --git a/src/member/tests.py b/src/member/tests.py index e32fe9d0..b5a01122 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -17,29 +17,29 @@ def setUp(self): def test_str(self): user = get_user_model()(username="test-str", email="test-str@example.org") - self.assertEquals(str(user), user.username) + self.assertEqual(str(user), user.username) def test_self_status(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("member-self")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_self_status_unauth(self): response = self.client.get(reverse("member-self")) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_self_change_email(self): self.client.force_authenticate(self.user) response = self.client.put( reverse("member-self"), data={"email": "test-self2@example.org", "username": "test-self"} ) - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(get_user_model().objects.get(id=self.user.id).email, "test-self2@example.org") + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(get_user_model().objects.get(id=self.user.id).email, "test-self2@example.org") def test_self_change_email_invalid(self): self.client.force_authenticate(self.user) response = self.client.put(reverse("member-self"), data={"email": "test-self"}) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_self_change_email_token_change(self): pr_token = self.user.password_reset_token @@ -55,7 +55,7 @@ def test_self_change_email_token_change(self): def test_self_get_email(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("member-self")) - self.assertEquals(response.data["email"], "test-self@example.org") + self.assertEqual(response.data["email"], "test-self@example.org") class MemberViewSetTestCase(APITestCase): @@ -76,7 +76,7 @@ def test_visible_admin(self): user.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("member-list")) - self.assertEquals(len(response.data["d"]["results"]), 3) + self.assertEqual(len(response.data["d"]["results"]), 3) def test_visible_not_admin(self): user = get_user_model()( @@ -86,7 +86,7 @@ def test_visible_not_admin(self): user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("member-list")) - self.assertEquals(len(response.data["d"]["results"]), 0) + self.assertEqual(len(response.data["d"]["results"]), 0) def test_visible_detail_admin(self): user = get_user_model()( @@ -96,7 +96,7 @@ def test_visible_detail_admin(self): user.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("member-detail", kwargs={"pk": user.id})) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_visible_detail_not_admin(self): user = get_user_model()( @@ -106,7 +106,7 @@ def test_visible_detail_not_admin(self): user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("member-detail", kwargs={"pk": user.id})) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_view_email_admin(self): self.client.force_authenticate(self.admin_user) @@ -129,7 +129,7 @@ def test_view_member(self): response = self.client.get( reverse("member-detail", kwargs={"pk": self.admin_user.id}) ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_patch_member(self): self.admin_user.is_visible = True @@ -139,7 +139,7 @@ def test_patch_member(self): reverse("member-detail", kwargs={"pk": self.admin_user.id}), data={"username": "test"}, ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_patch_member_admin(self): self.client.force_authenticate(self.admin_user) @@ -147,4 +147,4 @@ def test_patch_member_admin(self): reverse("member-detail", kwargs={"pk": self.user.id}), data={"username": "test"}, ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) diff --git a/src/plugins/tests.py b/src/plugins/tests.py index b3c01ad2..01212aa3 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -147,7 +147,7 @@ def setUp(self): self.plugin = BasicPointsPlugin(challenge) def test_points(self): - self.assertEquals(self.plugin.get_points(None, None, None), 1000) + self.assertEqual(self.plugin.get_points(None, None, None), 1000) class DecayPointsPluginTestCase(ChallengeSetupMixin, APITestCase): @@ -161,13 +161,13 @@ def setUp(self): self.plugin = DecayPointsPlugin(self.challenge2) def test_base_points(self): - self.assertEquals(self.plugin.get_points(None, None, 0), 1000) + self.assertEqual(self.plugin.get_points(None, None, 0), 1000) def test_min_points(self): - self.assertEquals(self.plugin.get_points(None, None, 1000000000), 100) + self.assertEqual(self.plugin.get_points(None, None, 1000000000), 100) def test_first_solve_points(self): - self.assertEquals(self.plugin.get_points(None, None, 1), 1000) + self.assertEqual(self.plugin.get_points(None, None, 1), 1000) def test_decaying_points(self): self.assertTrue(self.plugin.get_points(None, None, 1) > self.plugin.get_points(None, None, 5)) @@ -201,14 +201,14 @@ def test_recalculate(self): def test_score(self): self.plugin.score(self.user, self.team, '', Solve.objects.filter(challenge=self.challenge2)) - self.assertEquals(self.team.points, 1000) - self.assertEquals(self.team.leaderboard_points, 1000) + self.assertEqual(self.team.points, 1000) + self.assertEqual(self.team.leaderboard_points, 1000) def test_score_lb_disabled(self): config.config.set('enable_scoring', False) self.plugin.score(self.user, self.team, '', Solve.objects.filter(challenge=self.challenge2)) - self.assertEquals(self.team.points, 1000) - self.assertEquals(self.team.leaderboard_points, 0) + self.assertEqual(self.team.points, 1000) + self.assertEqual(self.team.leaderboard_points, 0) class PluginLoaderTestCase(APITestCase): @@ -216,6 +216,6 @@ class PluginLoaderTestCase(APITestCase): def test_plugin_loader(self): plugins.load_plugins(['plugins.tests']) # TODO: why is this loading 5 - # self.assertEquals(len(plugins.plugins['flag']), 4) - self.assertEquals(len(plugins.plugins['points']), 2) + # self.assertEqual(len(plugins.plugins['flag']), 4) + self.assertEqual(len(plugins.plugins['points']), 2) diff --git a/src/scorerecalculator/tests.py b/src/scorerecalculator/tests.py index d73d72d1..af56b803 100644 --- a/src/scorerecalculator/tests.py +++ b/src/scorerecalculator/tests.py @@ -33,21 +33,21 @@ def test_unauthed(self): response = self.client.post( reverse("recalculate-user", kwargs={"id": self.user.id}) ) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): self.client.force_authenticate(self.admin_user) response = self.client.post( reverse("recalculate-user", kwargs={"id": self.user.id}) ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_not_admin(self): self.client.force_authenticate(self.user) response = self.client.post( reverse("recalculate-user", kwargs={"id": self.user.id}) ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_recalculate(self): total = 0 @@ -60,7 +60,7 @@ def test_recalculate(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) - self.assertEquals( + self.assertEqual( get_user_model().objects.get(id=self.user.id).points, total + 100 ) @@ -75,7 +75,7 @@ def test_recalculate_leaderboard(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) - self.assertEquals( + self.assertEqual( get_user_model().objects.get(id=self.user.id).leaderboard_points, total ) @@ -104,21 +104,21 @@ def test_unauthed(self): response = self.client.post( reverse("recalculate-team", kwargs={"id": self.team.id}) ) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): self.client.force_authenticate(self.admin_user) response = self.client.post( reverse("recalculate-team", kwargs={"id": self.team.id}) ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_not_admin(self): self.client.force_authenticate(self.user) response = self.client.post( reverse("recalculate-team", kwargs={"id": self.team.id}) ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_recalculate(self): total = 0 @@ -131,7 +131,7 @@ def test_recalculate(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) - self.assertEquals(Team.objects.get(id=self.team.id).points, total + 100) + self.assertEqual(Team.objects.get(id=self.team.id).points, total + 100) def test_recalculate_leaderboard(self): total = 0 @@ -144,7 +144,7 @@ def test_recalculate_leaderboard(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) - self.assertEquals(Team.objects.get(id=self.team.id).leaderboard_points, total) + self.assertEqual(Team.objects.get(id=self.team.id).leaderboard_points, total) class RecalculateAllViewTestCase(APITestCase): @@ -169,17 +169,17 @@ def setUp(self): def test_unauthed(self): response = self.client.post(reverse("recalculate-all")) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): self.client.force_authenticate(self.admin_user) response = self.client.post(reverse("recalculate-all")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_not_admin(self): self.client.force_authenticate(self.user) response = self.client.post(reverse("recalculate-all")) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_recalculate(self): total = 0 @@ -192,8 +192,8 @@ def test_recalculate(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-all")) - self.assertEquals(Team.objects.get(id=self.team.id).points, total + 100) - self.assertEquals( + self.assertEqual(Team.objects.get(id=self.team.id).points, total + 100) + self.assertEqual( get_user_model().objects.get(id=self.user.id).points, total + 100 ) @@ -208,7 +208,7 @@ def test_recalculate_leaderboard(self): ).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-all")) - self.assertEquals(Team.objects.get(id=self.team.id).leaderboard_points, total) - self.assertEquals( + self.assertEqual(Team.objects.get(id=self.team.id).leaderboard_points, total) + self.assertEqual( get_user_model().objects.get(id=self.user.id).leaderboard_points, total ) diff --git a/src/stats/tests.py b/src/stats/tests.py index f017e6ca..c6027de6 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -11,7 +11,7 @@ class CountdownTestCase(APITestCase): def test_unauthed(self): response = self.client.get(reverse("countdown")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): user = get_user_model()( @@ -20,20 +20,20 @@ def test_authed(self): user.save() self.client.force_authenticate(user) response = self.client.get(reverse("countdown")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) class StatsTestCase(APITestCase): def test_unauthed(self): response = self.client.get(reverse("stats")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): user = get_user_model()(username="stats-test", email="stats-test@example.org") user.save() self.client.force_authenticate(user) response = self.client.get(reverse("stats")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_team_average(self): user = get_user_model()(username="stats-test", email="stats-test@example.org") @@ -43,27 +43,27 @@ def test_team_average(self): team.save() response = self.client.get(reverse("stats")) - self.assertEquals(response.data["d"]["avg_members"], 1) + self.assertEqual(response.data["d"]["avg_members"], 1) class FullStatsTestCase(APITestCase): def test_unauthed(self): response = self.client.get(reverse("full")) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed_non_privileged(self): user = get_user_model()(username="stats-test", email="stats-test@example.org") user.save() self.client.force_authenticate(user) response = self.client.get(reverse("full")) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_authed(self): user = get_user_model()(username="stats-test", email="stats-test@example.org", is_superuser=True, is_staff=True) user.save() self.client.force_authenticate(user) response = self.client.get(reverse("full")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_team_point_distribution(self): user = get_user_model()(username="stats-test", email="stats-test@example.org", is_superuser=True, is_staff=True) @@ -81,8 +81,8 @@ def test_team_point_distribution(self): self.client.force_authenticate(user) response = self.client.get(reverse("full")) - self.assertEquals(response.data["d"]["team_point_distribution"][0], 2) - self.assertEquals(response.data["d"]["team_point_distribution"][5], 1) + self.assertEqual(response.data["d"]["team_point_distribution"][0], 2) + self.assertEqual(response.data["d"]["team_point_distribution"][5], 1) def test_challenge_data(self): user = get_user_model()(username="stats-test", email="stats-test@example.org", is_superuser=True, is_staff=True) @@ -108,38 +108,38 @@ def test_challenge_data(self): config.config.set('enable_caching', False) response = self.client.get(reverse("full")) config.config.set('enable_caching', True) - self.assertEquals(response.data["d"]["challenges"][chall.id]["incorrect"], 1) - self.assertEquals(response.data["d"]["challenges"][chall.id]["correct"], 1) + self.assertEqual(response.data["d"]["challenges"][chall.id]["incorrect"], 1) + self.assertEqual(response.data["d"]["challenges"][chall.id]["correct"], 1) class CommitTestCase(APITestCase): def test_unauthed(self): response = self.client.get(reverse("version")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): user = get_user_model()(username="commit-test", email="commit-test@example.org") user.save() self.client.force_authenticate(user) response = self.client.get(reverse("version")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) class PrometheusTestCase(APITestCase): def test_unauthed(self): response = self.client.get(reverse("prometheus")) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): user = get_user_model()(username="prometheus-test", email="prometheus-test@example.org") user.save() self.client.force_authenticate(user) response = self.client.get(reverse("prometheus")) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_authed_admin(self): user = get_user_model()(username="prometheus-test-admin", email="prometheus-test-admin@example.org", is_staff=True, is_superuser=True) user.save() self.client.force_authenticate(user) response = self.client.get(reverse("prometheus")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) diff --git a/src/team/tests.py b/src/team/tests.py index 69054d53..9e54b8a4 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -39,27 +39,27 @@ class TeamSelfTestCase(TeamSetupMixin, APITestCase): def test_team_self(self): self.client.force_authenticate(user=self.user) response = self.client.get(reverse("team-self")) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_team_password(self): self.client.force_authenticate(user=self.user) response = self.client.get(reverse("team-self")) - self.assertEquals(response.data["password"], "abc") + self.assertEqual(response.data["password"], "abc") def test_no_team(self): self.client.force_authenticate(user=self.admin_user) response = self.client.get(reverse("team-self")) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_not_authed(self): response = self.client.get(reverse("team-self")) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_update(self): self.client.force_authenticate(user=self.user) response = self.client.patch(reverse("team-self"), data={"name": "name-change"}) - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(response.data['name'], "name-change") + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data['name'], "name-change") def test_update_not_owner(self): self.admin_user.team = self.team @@ -67,14 +67,14 @@ def test_update_not_owner(self): self.admin_user.save() self.client.force_authenticate(user=self.admin_user) response = self.client.patch(reverse("team-self"), data={"name": "name-change"}) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_disabled(self): self.client.force_authenticate(user=self.user) config.config.set("enable_team_leave", False) response = self.client.post(reverse("team-leave")) config.config.set("enable_team_leave", True) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_challenge_solved(self): config.config.set("enable_team_leave", True) @@ -96,7 +96,7 @@ def test_team_leave_challenge_solved(self): Solve.objects.create(solved_by=self.user, flag="", challenge=chall) response = self.client.post(reverse("team-leave")) config.config.set("enable_team_leave", False) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_with_members(self): self.client.force_authenticate(user=self.user) @@ -108,7 +108,7 @@ def test_team_leave_as_owner_with_members(self): config.config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) config.config.set("enable_team_leave", False) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_without_members(self): self.client.force_authenticate(user=self.user) @@ -116,7 +116,7 @@ def test_team_leave_as_owner_without_members(self): config.config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) config.config.set("enable_team_leave", False) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) class CreateTeamTestCase(TeamSetupMixin, APITestCase): @@ -125,27 +125,27 @@ def test_create_team(self): response = self.client.post( reverse("team-create"), data={"name": "test-team", "password": "test"} ) - self.assertEquals(response.status_code, HTTP_201_CREATED) + self.assertEqual(response.status_code, HTTP_201_CREATED) def test_create_team_in_team(self): self.client.force_authenticate(self.user) response = self.client.post( reverse("team-create"), data={"name": "test-team", "password": "test"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create_team_not_authed(self): response = self.client.post( reverse("team-create"), data={"name": "test-team", "password": "test"} ) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_create_duplicate_team(self): self.client.force_authenticate(self.admin_user) response = self.client.post( reverse("team-create"), data={"name": "team-test", "password": "test"} ) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) class JoinTeamTestCase(TeamSetupMixin, APITestCase): @@ -154,21 +154,21 @@ def test_join_team(self): response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_join_team_incorrect_password(self): self.client.force_authenticate(self.admin_user) response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "incorrect_pass"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_incorrect_name(self): self.client.force_authenticate(self.admin_user) response = self.client.post( reverse("team-join"), data={"name": "incorrect-team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_join_team_full(self): user2 = get_user_model()( @@ -184,7 +184,7 @@ def test_join_team_full(self): response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_disabled(self): self.client.force_authenticate(self.admin_user) @@ -193,7 +193,7 @@ def test_join_team_disabled(self): reverse("team-join"), data={"name": "team-test", "password": "abc"} ) config.config.set("enable_team_join", True) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_duplicate(self): self.client.force_authenticate(self.admin_user) @@ -203,25 +203,25 @@ def test_join_team_duplicate(self): response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_not_authed(self): response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_join_team_team_owner(self): self.client.force_authenticate(self.user) response = self.client.post( reverse("team-join"), data={"name": "team-test", "password": "abc"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_malformed(self): self.client.force_authenticate(self.admin_user) response = self.client.post(reverse("team-join"), data={"name": "team-test"}) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) class TeamViewsetTestCase(TeamSetupMixin, APITestCase): @@ -230,7 +230,7 @@ def test_visible_admin(self): self.team.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("team-list")) - self.assertEquals(len(response.data['d']["results"]), 1) + self.assertEqual(len(response.data['d']["results"]), 1) def test_visible_not_admin(self): self.team.is_visible = False @@ -238,21 +238,21 @@ def test_visible_not_admin(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("team-list")) - self.assertEquals(len(response.data["d"]["results"]), 0) + self.assertEqual(len(response.data["d"]["results"]), 0) def test_visible_detail_admin(self): self.team.is_visible = False self.team.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("team-detail", kwargs={"pk": self.team.id})) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_visible_detail_not_admin(self): self.team.is_visible = False self.team.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("team-detail", kwargs={"pk": self.team.id})) - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_view_password_admin(self): self.client.force_authenticate(self.admin_user) @@ -269,18 +269,18 @@ def test_view_password_not_admin(self): def test_view_team(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("team-detail", kwargs={"pk": self.team.id})) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) def test_patch_team(self): self.client.force_authenticate(self.user) response = self.client.patch( reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"} ) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_patch_team_admin(self): self.client.force_authenticate(self.admin_user) response = self.client.patch( reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"} ) - self.assertEquals(response.status_code, HTTP_200_OK) + self.assertEqual(response.status_code, HTTP_200_OK) From 2111f4bd347ca853455f81a8fbd16c7703eb7c53 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 12:55:25 +0100 Subject: [PATCH 037/185] Remove deprecated 'assertNotEquals' calls --- src/authentication/tests.py | 2 +- src/hint/tests.py | 2 +- src/member/tests.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 7fe67422..9392f026 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -516,7 +516,7 @@ def test_add_2fa(self): self.client.force_authenticate(user=self.user) self.client.post(reverse('add-2fa')) self.assertFalse(self.user.has_2fa()) - self.assertNotEquals(self.user.totp_device, None) + self.assertNotEqual(self.user.totp_device, None) def test_add_2fa_twice(self): self.client.force_authenticate(user=self.user) diff --git a/src/hint/tests.py b/src/hint/tests.py index a06c55ea..667acc3d 100644 --- a/src/hint/tests.py +++ b/src/hint/tests.py @@ -108,7 +108,7 @@ def test_hint_use_read(self): self.client.post(reverse("hint-use"), data={"id": self.hint3.id}) response = self.client.get(reverse("hint-detail", kwargs={"pk": self.hint3.id})) self.assertEqual(response.status_code, HTTP_200_OK) - self.assertNotEquals(response.data["text"], "") + self.assertNotEqual(response.data["text"], "") def test_hint_use_duplicate(self): self.client.force_authenticate(self.user) diff --git a/src/member/tests.py b/src/member/tests.py index b5a01122..5be9b998 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -49,8 +49,8 @@ def test_self_change_email_token_change(self): reverse("member-self"), data={"email": "test-self3@example.org"} ) user = get_user_model().objects.get(id=self.user.id) - self.assertNotEquals(pr_token, user.password_reset_token) - self.assertNotEquals(ev_token, user.email_token) + self.assertNotEqual(pr_token, user.password_reset_token) + self.assertNotEqual(ev_token, user.email_token) def test_self_get_email(self): self.client.force_authenticate(self.user) From f75deac60deed0e5b66c6b970048c4224e823160 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 13:29:50 +0100 Subject: [PATCH 038/185] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bf474d82..44548bde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ python_files = "tests.py test_*.py *_tests.py" [tool.black] exclude = 'migrations' +line_length = 200 [build-system] requires = ["poetry-core>=1.0.0a5"] From ca97f03246a1e0eb477850e8aa131f515b3b8b9f Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 13:31:02 +0100 Subject: [PATCH 039/185] Modularize tests --- src/challenge/tests.py | 365 ------------------------------ src/challenge/tests/mixins.py | 65 ++++++ src/challenge/tests/test_views.py | 343 ++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+), 365 deletions(-) delete mode 100644 src/challenge/tests.py create mode 100644 src/challenge/tests/mixins.py create mode 100644 src/challenge/tests/test_views.py diff --git a/src/challenge/tests.py b/src/challenge/tests.py deleted file mode 100644 index b4a31de5..00000000 --- a/src/challenge/tests.py +++ /dev/null @@ -1,365 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.models import AnonymousUser -from django.urls import reverse -from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED, \ - HTTP_400_BAD_REQUEST -from rest_framework.test import APITestCase - -from challenge.models import Category, Challenge, Solve -import config -from hint.models import Hint, HintUse -from team.models import Team - - -class ChallengeSetupMixin: - - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') - category.save() - challenge1 = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge2 = Challenge(name='test2', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge3 = Challenge(name='test3', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge1.save() - challenge2.save() - challenge3.save() - challenge1.unlock_requirements = str(challenge2.id) - challenge1.save() - hint1 = Hint(name='hint1', challenge=challenge1, text='a', penalty=100) - hint2 = Hint(name='hint2', challenge=challenge1, text='a', penalty=100) - hint3 = Hint(name='hint3', challenge=challenge2, text='a', penalty=100) - hint1.save() - hint2.save() - hint3.save() - user = get_user_model()(username='challenge-test', email='challenge-test@example.org') - user.save() - team = Team(name='team', password='password', owner=user) - team.save() - user.team = team - user.save() - user2 = get_user_model()(username='challenge-test-2', email='challenge-test-2@example.org') - user2.team = team - user2.save() - user3 = get_user_model()(username='challenge-test-3', email='challenge-test-3@example.org') - user3.save() - team2 = Team(name='team2', password='password', owner=user3) - team2.save() - user3.team = team2 - user3.save() - self.user = user - self.user2 = user2 - self.user3 = user3 - self.team = team - self.team2 = team2 - self.category = category - self.challenge1 = challenge1 - self.challenge2 = challenge2 - self.challenge3 = challenge3 - self.hint1 = hint1 - self.hint2 = hint2 - self.hint3 = hint3 - - -class ChallengeTestCase(ChallengeSetupMixin, APITestCase): - - def solve_challenge(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - return self.client.post(reverse('submit-flag'), data) - - def test_challenge_solve(self): - response = self.solve_challenge() - self.assertEqual(response.status_code, HTTP_200_OK) - self.assertEqual(response.data['d']['correct'], True) - - def test_challenge_solve_incorrect_flag(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{b}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEqual(response.status_code, HTTP_200_OK) - self.assertEqual(response.data['d']['correct'], False) - - def test_challenge_double_solve(self): - self.solve_challenge() - self.client.force_authenticate(user=self.user2) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - - def test_challenge_unlocks(self): - self.solve_challenge() - self.challenge1.unlock_requirements = str(self.challenge2.id) - self.assertTrue(self.challenge1.is_unlocked(get_user_model().objects.get(id=self.user.id))) - - def test_challenge_unlocks_no_team(self): - user4 = get_user_model()(username='challenge-test-4', email='challenge-test-4@example.org') - user4.save() - self.assertFalse(self.challenge1.is_unlocked(user4)) - - def test_challenge_unlocks_locked(self): - self.assertFalse(self.challenge1.is_unlocked(self.user)) - - def test_hint_scoring(self): - HintUse(hint=self.hint3, team=self.team, user=self.user, challenge=self.challenge2).save() - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEqual(response.data['solves'][0]['points'], 900) - - def test_solve_first_blood(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEqual(response.data['solves'][0]['first_blood'], True) - - def test_solve_solved_by_name(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEqual(response.data['solves'][0]['solved_by_name'], 'challenge-test') - - def test_solve_team_name(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEqual(response.data['solves'][0]['team_name'], 'team') - - def test_normal_scoring(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEqual(response.data['solves'][0]['points'], 1000) - - def test_is_solved(self): - self.solve_challenge() - self.assertTrue(self.challenge2.is_solved(user=self.user)) - - def test_is_not_solved(self): - self.assertFalse(self.challenge1.is_solved(user=self.user)) - - def test_submission_disabled(self): - config.config.set('enable_flag_submission', False) - response = self.solve_challenge() - config.config.set('enable_flag_submission', True) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - - def test_submission_malformed(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) - - def test_challenge_score_same_team(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - self.client.post(reverse('submit-flag'), data) - self.client.force_authenticate(user=self.user2) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - - def test_challenge_score_not_first_blood(self): - self.solve_challenge() - self.client.force_authenticate(user=self.user3) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEqual(response.status_code, HTTP_200_OK) - self.assertFalse(Solve.objects.get(team=self.team2, challenge=self.challenge2).first_blood) - - def test_challenge_solved_unauthed(self): - self.assertFalse(self.challenge2.is_solved(AnonymousUser())) - - def test_challenge_unlocked_unauthed(self): - self.assertFalse(self.challenge2.is_unlocked(AnonymousUser())) - - def test_challenge_solved_no_team(self): - user4 = get_user_model()(username='challenge-test-4', email='challenge-test-4@example.org') - user4.save() - self.assertFalse(self.challenge2.is_solved(user4)) - - -class CategoryViewsetTestCase(ChallengeSetupMixin, APITestCase): - - def test_category_list_unauthenticated_permission(self): - response = self.client.get(reverse('categories-list')) - self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) - - def test_category_list_authenticated_permission(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertEqual(response.status_code, HTTP_200_OK) - - def test_category_list_unauthenticated_content(self): - response = self.client.get(reverse('categories-list')) - self.assertFalse(response.data['s']) - self.assertEqual(response.data['m'], 'not_authenticated') - self.assertEqual(response.data['d'], '') - - def test_category_list_authenticated_content(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertEqual(len(response.data['d']), 1) - self.assertEqual(len(response.data['d'][0]['challenges']), 3) - - def test_category_list_challenge_redacting(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertFalse('description' in response.data['d'][0]['challenges'][0]) - - def test_category_list_challenge_redacting_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertTrue('description' in response.data['d'][0]['challenges'][0]) - - def test_category_list_challenge_unlocked_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertFalse(response.data['d'][0]['challenges'][0]['unlocked']) - - def test_category_create(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('categories-list'), data={ - 'name': 'test-category-2', - 'contained_type': 'test', - 'description': 'test', - }) - self.assertTrue(response.status_code, HTTP_200_OK) - - def test_category_create_unauthorized(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('categories-list'), data={ - 'name': 'test-category-2', - 'contained_type': 'test', - 'description': 'test', - }) - self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) - - -class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): - - def test_challenge_list_unauthenticated_permission(self): - response = self.client.get(reverse('challenges-list')) - self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) - - def test_challenge_list_authenticated_permission(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertEqual(response.status_code, HTTP_200_OK) - - def test_challenge_list_unauthenticated_content(self): - response = self.client.get(reverse('challenges-list')) - self.assertFalse(response.data['s']) - self.assertEqual(response.data['m'], 'not_authenticated') - self.assertEqual(response.data['d'], '') - - def test_challenge_list_authenticated_content(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertEqual(len(response.data), 3) - - def test_challenge_list_challenge_redacting(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertFalse('description' in response.data[-1]) - - def test_challenge_list_challenge_redacting_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertTrue('description' in response.data[0]) - - def test_challenge_list_challenge_unlocked_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - # TODO: Don't depend on order - self.assertFalse(response.data[-1]['unlocked']) - - def test_single_challenge_redacting(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertFalse('description' in response.data) - - def test_single_challenge_admin_redacting(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertTrue('description' in response.data) - - def test_admin_unlocking(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertFalse(response.data['unlocked']) - - def test_user_post_detail(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - - def test_user_post_list(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list')) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - - def test_create_challenge(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list'), data={ - 'name': 'test4', 'category': self.category.id, 'description': 'abc', - 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'score': 1000, 'unlock_requirements': "", 'flag_metadata': {}, - 'tags': [], - }, format='json') - self.assertEqual(response.status_code, HTTP_201_CREATED) - - def test_create_challenge_unauthorized(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list'), data={ - 'name': 'test4', 'category': self.category.id, 'description': 'abc', - 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'score': 1000, 'unlock_requirements': "a", 'flag_metadata': {} - }, format='json') - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py new file mode 100644 index 00000000..1ea38d02 --- /dev/null +++ b/src/challenge/tests/mixins.py @@ -0,0 +1,65 @@ +from challenge.models import Category, Challenge +from hint.models import Hint +from member.models import Member +from team.models import Team + + +class ChallengeSetupMixin: + """ + Mixin to create dummy challenge objects for use in tests. + + TODO: Deprecate in favour of Model factories and Faker(). + """ + + def setUp(self) -> None: + """Create dummy challenges and any relevant related models.""" + self.category = Category.objects.create(name="test", display_order=0, contained_type="test", description="") + self.challenge2 = Challenge.objects.create( + name="test2", + category=self.category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "ractf{a}"}, + author="dave", + score=1000, + ) + self.challenge1 = Challenge.objects.create( + name="test1", + category=self.category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "ractf{a}"}, + author="dave", + score=1000, + unlock_requirements=self.challenge2.id, + ) + self.challenge3 = Challenge.objects.create( + name="test3", + category=self.category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "ractf{a}"}, + author="dave", + score=1000, + ) + self.hint1 = Hint.objects.create(name="hint1", challenge=self.challenge1, text="a", penalty=100) + self.hint2 = Hint.objects.create(name="hint2", challenge=self.challenge1, text="a", penalty=100) + self.hint3 = Hint.objects.create(name="hint3", challenge=self.challenge2, text="a", penalty=100) + + self.user = Member.objects.create(username="challenge-test", email="challenge-test@example.org") + self.team = Team.objects.create(name="team", password="password", owner=self.user) + self.user.team = self.team + self.user.save() + + self.user2 = Member.objects.create(username="challenge-test-2", email="challenge-test-2@example.org", team=self.team) + self.user3 = Member.objects.create(username="challenge-test-3", email="challenge-test-3@example.org") + + self.team2 = Team.objects.create(name="team2", password="password", owner=self.user3) + self.user3.team = self.team2 + self.user3.save() diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py new file mode 100644 index 00000000..ce8611af --- /dev/null +++ b/src/challenge/tests/test_views.py @@ -0,0 +1,343 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser +from django.urls import reverse + +from rest_framework.status import ( + HTTP_200_OK, + HTTP_403_FORBIDDEN, + HTTP_401_UNAUTHORIZED, + HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, +) +from rest_framework.test import APITestCase + +import config + +from challenge.models import Solve +from challenge.tests.mixins import ChallengeSetupMixin +from hint.models import HintUse + + +class ChallengeTestCase(ChallengeSetupMixin, APITestCase): + def solve_challenge(self): + self.client.force_authenticate(user=self.user) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + return self.client.post(reverse("submit-flag"), data) + + def test_challenge_solve(self): + response = self.solve_challenge() + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data["d"]["correct"], True) + + def test_challenge_solve_incorrect_flag(self): + self.client.force_authenticate(user=self.user) + data = { + "flag": "ractf{b}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data["d"]["correct"], False) + + def test_challenge_double_solve(self): + self.solve_challenge() + self.client.force_authenticate(user=self.user2) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_challenge_unlocks(self): + self.solve_challenge() + self.challenge1.unlock_requirements = str(self.challenge2.id) + self.assertTrue(self.challenge1.is_unlocked(get_user_model().objects.get(id=self.user.id))) + + def test_challenge_unlocks_no_team(self): + user4 = get_user_model()(username="challenge-test-4", email="challenge-test-4@example.org") + user4.save() + self.assertFalse(self.challenge1.is_unlocked(user4)) + + def test_challenge_unlocks_locked(self): + self.assertFalse(self.challenge1.is_unlocked(self.user)) + + def test_hint_scoring(self): + HintUse(hint=self.hint3, team=self.team, user=self.user, challenge=self.challenge2).save() + self.solve_challenge() + response = self.client.get(reverse("team-self")) + self.assertEqual(response.data["solves"][0]["points"], 900) + + def test_solve_first_blood(self): + self.solve_challenge() + response = self.client.get(reverse("team-self")) + self.assertEqual(response.data["solves"][0]["first_blood"], True) + + def test_solve_solved_by_name(self): + self.solve_challenge() + response = self.client.get(reverse("team-self")) + self.assertEqual(response.data["solves"][0]["solved_by_name"], "challenge-test") + + def test_solve_team_name(self): + self.solve_challenge() + response = self.client.get(reverse("team-self")) + self.assertEqual(response.data["solves"][0]["team_name"], "team") + + def test_normal_scoring(self): + self.solve_challenge() + response = self.client.get(reverse("team-self")) + self.assertEqual(response.data["solves"][0]["points"], 1000) + + def test_is_solved(self): + self.solve_challenge() + self.assertTrue(self.challenge2.is_solved(user=self.user)) + + def test_is_not_solved(self): + self.assertFalse(self.challenge1.is_solved(user=self.user)) + + def test_submission_disabled(self): + config.config.set("enable_flag_submission", False) + response = self.solve_challenge() + config.config.set("enable_flag_submission", True) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_submission_malformed(self): + self.client.force_authenticate(user=self.user) + data = { + "flag": "ractf{a}", + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) + + def test_challenge_score_same_team(self): + self.client.force_authenticate(user=self.user) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + self.client.post(reverse("submit-flag"), data) + self.client.force_authenticate(user=self.user2) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_challenge_score_not_first_blood(self): + self.solve_challenge() + self.client.force_authenticate(user=self.user3) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertFalse(Solve.objects.get(team=self.team2, challenge=self.challenge2).first_blood) + + def test_challenge_solved_unauthed(self): + self.assertFalse(self.challenge2.is_solved(AnonymousUser())) + + def test_challenge_unlocked_unauthed(self): + self.assertFalse(self.challenge2.is_unlocked(AnonymousUser())) + + def test_challenge_solved_no_team(self): + user4 = get_user_model()(username="challenge-test-4", email="challenge-test-4@example.org") + user4.save() + self.assertFalse(self.challenge2.is_solved(user4)) + + +class CategoryViewsetTestCase(ChallengeSetupMixin, APITestCase): + def test_category_list_unauthenticated_permission(self): + response = self.client.get(reverse("categories-list")) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) + + def test_category_list_authenticated_permission(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + self.assertEqual(response.status_code, HTTP_200_OK) + + def test_category_list_unauthenticated_content(self): + response = self.client.get(reverse("categories-list")) + self.assertFalse(response.data["s"]) + self.assertEqual(response.data["m"], "not_authenticated") + self.assertEqual(response.data["d"], "") + + def test_category_list_authenticated_content(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + self.assertEqual(len(response.data["d"]), 1) + self.assertEqual(len(response.data["d"][0]["challenges"]), 3) + + def test_category_list_challenge_redacting(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + self.assertFalse("description" in response.data["d"][0]["challenges"][0]) + + def test_category_list_challenge_redacting_admin(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + self.assertTrue("description" in response.data["d"][0]["challenges"][0]) + + def test_category_list_challenge_unlocked_admin(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + print(response.data["d"]) + self.assertFalse(response.data["d"][0]["challenges"][0]["unlocked"]) + + def test_category_create(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post( + reverse("categories-list"), + data={ + "name": "test-category-2", + "contained_type": "test", + "description": "test", + }, + ) + self.assertTrue(response.status_code, HTTP_200_OK) + + def test_category_create_unauthorized(self): + self.user.is_staff = False + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post( + reverse("categories-list"), + data={ + "name": "test-category-2", + "contained_type": "test", + "description": "test", + }, + ) + self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) + + +class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): + def test_challenge_list_unauthenticated_permission(self): + response = self.client.get(reverse("challenges-list")) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) + + def test_challenge_list_authenticated_permission(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-list")) + self.assertEqual(response.status_code, HTTP_200_OK) + + def test_challenge_list_unauthenticated_content(self): + response = self.client.get(reverse("challenges-list")) + self.assertFalse(response.data["s"]) + self.assertEqual(response.data["m"], "not_authenticated") + self.assertEqual(response.data["d"], "") + + def test_challenge_list_authenticated_content(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-list")) + self.assertEqual(len(response.data), 3) + + def test_challenge_list_challenge_redacting(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-list")) + self.assertFalse("description" in response.data[-1]) + + def test_challenge_list_challenge_redacting_admin(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-list")) + self.assertTrue("description" in response.data[0]) + + def test_challenge_list_challenge_unlocked_admin(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-list")) + # TODO: Don't depend on order + self.assertFalse(response.data[-1]["unlocked"]) + + def test_single_challenge_redacting(self): + self.user.is_staff = False + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-detail", kwargs={"pk": self.challenge1.id})) + self.assertFalse("description" in response.data) + + def test_single_challenge_admin_redacting(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-detail", kwargs={"pk": self.challenge1.id})) + self.assertTrue("description" in response.data) + + def test_admin_unlocking(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.get(reverse("challenges-detail", kwargs={"pk": self.challenge1.id})) + self.assertFalse(response.data["unlocked"]) + + def test_user_post_detail(self): + self.user.is_staff = False + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post(reverse("challenges-detail", kwargs={"pk": self.challenge1.id})) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_user_post_list(self): + self.user.is_staff = False + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post(reverse("challenges-list")) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_create_challenge(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post( + reverse("challenges-list"), + data={ + "name": "test4", + "category": self.category.id, + "description": "abc", + "challenge_type": "test", + "challenge_metadata": {}, + "flag_type": "plaintext", + "author": "dave", + "score": 1000, + "unlock_requirements": "", + "flag_metadata": {}, + "tags": [], + }, + format="json", + ) + self.assertEqual(response.status_code, HTTP_201_CREATED) + + def test_create_challenge_unauthorized(self): + self.user.is_staff = False + self.user.save() + self.client.force_authenticate(self.user) + response = self.client.post( + reverse("challenges-list"), + data={ + "name": "test4", + "category": self.category.id, + "description": "abc", + "challenge_type": "test", + "challenge_metadata": {}, + "flag_type": "plaintext", + "author": "dave", + "score": 1000, + "unlock_requirements": "a", + "flag_metadata": {}, + }, + format="json", + ) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) From 05ffc49923c756e84e887d5f8037387662ba2fdc Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 13:41:16 +0100 Subject: [PATCH 040/185] Update references to challenge.tests.mixins --- src/challenge/tests/__init__.py | 0 src/challenge/tests/test_views.py | 2 +- src/hint/tests.py | 7 +- src/plugins/tests.py | 149 +++++++++++++++--------------- 4 files changed, 81 insertions(+), 77 deletions(-) create mode 100644 src/challenge/tests/__init__.py diff --git a/src/challenge/tests/__init__.py b/src/challenge/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index ce8611af..26293c8e 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -14,8 +14,8 @@ import config from challenge.models import Solve -from challenge.tests.mixins import ChallengeSetupMixin from hint.models import HintUse +from challenge.tests.mixins import ChallengeSetupMixin class ChallengeTestCase(ChallengeSetupMixin, APITestCase): diff --git a/src/hint/tests.py b/src/hint/tests.py index 667acc3d..c267d332 100644 --- a/src/hint/tests.py +++ b/src/hint/tests.py @@ -2,16 +2,15 @@ from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_201_CREATED from rest_framework.test import APITestCase -from challenge.tests import ChallengeSetupMixin +from challenge.tests.mixins import ChallengeSetupMixin from hint.views import HintViewSet, UseHintView class HintTestCase(ChallengeSetupMixin, APITestCase): - def setUp(self): super().setUp() - HintViewSet.throttle_scope = '' - UseHintView.throttle_scope = '' + HintViewSet.throttle_scope = "" + UseHintView.throttle_scope = "" def test_hint_view(self): self.client.force_authenticate(self.user) diff --git a/src/plugins/tests.py b/src/plugins/tests.py index 01212aa3..c1c44691 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -2,7 +2,7 @@ from rest_framework.test import APITestCase from challenge.models import Category, Challenge, Solve, Score -from challenge.tests import ChallengeSetupMixin +from challenge.tests.mixins import ChallengeSetupMixin import config from plugins import plugins from plugins.flag.hashed import HashedFlagPlugin @@ -15,133 +15,139 @@ class HashedFlagPluginTestCase(APITestCase): - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', - flag_metadata={'flag': '9340563721a110d2c9175507f8947f111568cf21ef2aff545e3f93238f63ff32'}, - author='dave', score=1000) + challenge = Challenge( + name="test1", + category=category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "9340563721a110d2c9175507f8947f111568cf21ef2aff545e3f93238f63ff32"}, + author="dave", + score=1000, + ) challenge.save() self.challenge = challenge self.plugin = HashedFlagPlugin(self.challenge) def test_valid_flag(self): - self.assertTrue(self.plugin.check('ractf{a}')) + self.assertTrue(self.plugin.check("ractf{a}")) def test_invalid_flag(self): - self.assertFalse(self.plugin.check('ractf{b}')) + self.assertFalse(self.plugin.check("ractf{b}")) class LenientFlagPluginTestCase(APITestCase): - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', - flag_metadata={'flag': 'ractf{a}', 'exclude_passes': []}, - author='dave', score=1000) + challenge = Challenge( + name="test1", + category=category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "ractf{a}", "exclude_passes": []}, + author="dave", + score=1000, + ) challenge.save() self.challenge = challenge self.plugin = LenientFlagPlugin(self.challenge) def test_valid_flag(self): - self.assertTrue(self.plugin.check('ractf{a}')) + self.assertTrue(self.plugin.check("ractf{a}")) def test_valid_flag_accented(self): - self.assertTrue(self.plugin.check('ractf{à}')) + self.assertTrue(self.plugin.check("ractf{à}")) def test_valid_flag_invalid_format(self): - self.assertTrue(self.plugin.check('a')) + self.assertTrue(self.plugin.check("a")) def test_valid_flag_spaced(self): - self.assertTrue(self.plugin.check(' ractf{a} ')) + self.assertTrue(self.plugin.check(" ractf{a} ")) def test_valid_flag_casing(self): - self.assertTrue(self.plugin.check('A')) + self.assertTrue(self.plugin.check("A")) def test_valid_flag_all(self): - self.assertTrue(self.plugin.check(' Á')) + self.assertTrue(self.plugin.check(" Á")) def test_valid_flag_accented_excluded(self): - self.challenge.flag_metadata['exclude_passes'].append('accent_insensitive') + self.challenge.flag_metadata["exclude_passes"].append("accent_insensitive") self.challenge.save() - self.assertFalse(self.plugin.check('ractf{à}')) + self.assertFalse(self.plugin.check("ractf{à}")) def test_valid_flag_invalid_format_excluded(self): - self.challenge.flag_metadata['exclude_passes'].append('format') + self.challenge.flag_metadata["exclude_passes"].append("format") self.challenge.save() - self.assertFalse(self.plugin.check('a')) + self.assertFalse(self.plugin.check("a")) def test_valid_flag_spaced_excluded(self): - self.challenge.flag_metadata['exclude_passes'].append('whitespace_insensitive') + self.challenge.flag_metadata["exclude_passes"].append("whitespace_insensitive") self.challenge.save() - self.assertFalse(self.plugin.check(' ractf{a} ')) + self.assertFalse(self.plugin.check(" ractf{a} ")) def test_valid_flag_casing_excluded(self): - self.challenge.flag_metadata['exclude_passes'].append('case_insensitive') + self.challenge.flag_metadata["exclude_passes"].append("case_insensitive") self.challenge.save() - self.assertFalse(self.plugin.check('A')) + self.assertFalse(self.plugin.check("A")) def test_valid_flag_missing_metadata(self): - self.challenge.flag_metadata.pop('exclude_passes') - self.assertTrue(self.plugin.check('A')) + self.challenge.flag_metadata.pop("exclude_passes") + self.assertTrue(self.plugin.check("A")) class PlaintextFlagPluginTestCase(APITestCase): - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', - flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) + challenge = Challenge( + name="test1", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000 + ) challenge.save() self.challenge = challenge self.plugin = PlaintextFlagPlugin(self.challenge) def test_valid_flag(self): - self.assertTrue(self.plugin.check('ractf{a}')) + self.assertTrue(self.plugin.check("ractf{a}")) def test_invalid_flag(self): - self.assertFalse(self.plugin.check('ractf{b}')) + self.assertFalse(self.plugin.check("ractf{b}")) class RegexFlagPluginTestCase(APITestCase): - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', - flag_metadata={'flag': '.*ractf{a}.*'}, - author='dave', score=1000) + challenge = Challenge( + name="test1", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": ".*ractf{a}.*"}, author="dave", score=1000 + ) challenge.save() self.challenge = challenge self.plugin = RegexFlagPlugin(self.challenge) def test_valid_flag(self): - self.assertTrue(self.plugin.check('ractf{a}')) + self.assertTrue(self.plugin.check("ractf{a}")) def test_invalid_flag(self): - self.assertFalse(self.plugin.check('ractf{b}')) + self.assertFalse(self.plugin.check("ractf{b}")) def test_valid_flag_regex(self): - self.assertTrue(self.plugin.check('abcractf{a}abc')) + self.assertTrue(self.plugin.check("abcractf{a}abc")) class BasicPointsPluginTestCase(APITestCase): - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', - flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) + challenge = Challenge( + name="test1", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000 + ) challenge.save() self.challenge = challenge self.plugin = BasicPointsPlugin(challenge) @@ -151,12 +157,11 @@ def test_points(self): class DecayPointsPluginTestCase(ChallengeSetupMixin, APITestCase): - def setUp(self): super(DecayPointsPluginTestCase, self).setUp() self.challenge2.challenge_metadata = { - 'decay_constant': 0.5, - 'min_points': 100, + "decay_constant": 0.5, + "min_points": 100, } self.plugin = DecayPointsPlugin(self.challenge2) @@ -173,10 +178,10 @@ def test_decaying_points(self): self.assertTrue(self.plugin.get_points(None, None, 1) > self.plugin.get_points(None, None, 5)) def test_recalculate(self): - points = self.plugin.get_points(None, None, 0) - score = Score(team=self.team, reason='test', points=points) + points = self.plugin.get_points(None, None, 0) + score = Score(team=self.team, reason="test", points=points) score.save() - solve = Solve(team=self.team, solved_by=self.user, challenge=self.challenge2, score=score, flag='') + solve = Solve(team=self.team, solved_by=self.user, challenge=self.challenge2, score=score, flag="") solve.save() self.team.points += points self.team.leaderboard_points += points @@ -184,9 +189,9 @@ def test_recalculate(self): self.user.leaderboard_points += points self.team.save() self.user.save() - score = Score(team=self.team2, reason='test', points=points) + score = Score(team=self.team2, reason="test", points=points) score.save() - solve = Solve(team=self.team2, solved_by=self.user3, challenge=self.challenge2, score=score, flag='') + solve = Solve(team=self.team2, solved_by=self.user3, challenge=self.challenge2, score=score, flag="") solve.save() self.team2.points += points self.team2.leaderboard_points += points @@ -194,28 +199,28 @@ def test_recalculate(self): self.user3.leaderboard_points += points self.team2.save() self.user3.save() - self.plugin.recalculate(teams=Team.objects.filter(solves__challenge=self.challenge2), - users=get_user_model().objects.filter(solves__challenge=self.challenge2), - solves=Solve.objects.filter(challenge=self.challenge2)) + self.plugin.recalculate( + teams=Team.objects.filter(solves__challenge=self.challenge2), + users=get_user_model().objects.filter(solves__challenge=self.challenge2), + solves=Solve.objects.filter(challenge=self.challenge2), + ) self.assertTrue(get_user_model().objects.get(id=self.user.id).points < points) def test_score(self): - self.plugin.score(self.user, self.team, '', Solve.objects.filter(challenge=self.challenge2)) + self.plugin.score(self.user, self.team, "", Solve.objects.filter(challenge=self.challenge2)) self.assertEqual(self.team.points, 1000) self.assertEqual(self.team.leaderboard_points, 1000) def test_score_lb_disabled(self): - config.config.set('enable_scoring', False) - self.plugin.score(self.user, self.team, '', Solve.objects.filter(challenge=self.challenge2)) + config.config.set("enable_scoring", False) + self.plugin.score(self.user, self.team, "", Solve.objects.filter(challenge=self.challenge2)) self.assertEqual(self.team.points, 1000) self.assertEqual(self.team.leaderboard_points, 0) class PluginLoaderTestCase(APITestCase): - def test_plugin_loader(self): - plugins.load_plugins(['plugins.tests']) + plugins.load_plugins(["plugins.tests"]) # TODO: why is this loading 5 # self.assertEqual(len(plugins.plugins['flag']), 4) - self.assertEqual(len(plugins.plugins['points']), 2) - + self.assertEqual(len(plugins.plugins["points"]), 2) From 2a46d0d65572a8b10859513a9bc1c4f425aab29e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 13:42:14 +0100 Subject: [PATCH 041/185] Update mixins.py --- src/challenge/tests/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index 1ea38d02..b4924500 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -35,7 +35,7 @@ def setUp(self) -> None: flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000, - unlock_requirements=self.challenge2.id, + unlock_requirements=str(self.challenge2.id), ) self.challenge3 = Challenge.objects.create( name="test3", From 073c369424527689d85920b9b70b2e27cb86e117 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 14:10:16 +0100 Subject: [PATCH 042/185] Bump django-zxcvbn-password-validator --- poetry.lock | 805 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 401 insertions(+), 406 deletions(-) diff --git a/poetry.lock b/poetry.lock index f96c308c..b45fa27d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,81 +1,79 @@ [[package]] -category = "main" -description = "asyncio (PEP 3156) Redis support" name = "aioredis" +version = "1.3.1" +description = "asyncio (PEP 3156) Redis support" +category = "main" optional = false python-versions = "*" -version = "1.3.1" [package.dependencies] async-timeout = "*" hiredis = "*" [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Disable App Nap on macOS >= 10.9" -marker = "sys_platform == \"darwin\"" name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" optional = false python-versions = "*" -version = "0.1.2" [[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" name = "asgiref" +version = "3.3.1" +description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.5" -version = "3.3.1" [package.extras] tests = ["pytest", "pytest-asyncio"] [[package]] -category = "main" -description = "Timeout context manager for asyncio programs" name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.5.3" -version = "3.0.1" [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [[package]] -category = "main" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "main" -description = "WebSocket client & server library, WAMP real-time framework" name = "autobahn" +version = "20.12.3" +description = "WebSocket client & server library, WAMP real-time framework" +category = "main" optional = false python-versions = ">=3.6" -version = "20.12.3" [package.dependencies] cryptography = ">=2.9.2" @@ -95,23 +93,23 @@ twisted = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)"] xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] [[package]] -category = "dev" -description = "Removes unused imports and unused variables" name = "autoflake" +version = "1.4" +description = "Removes unused imports and unused variables" +category = "dev" optional = false python-versions = "*" -version = "1.4" [package.dependencies] pyflakes = ">=1.1.0" [[package]] -category = "main" -description = "Self-service finite-state machines for the programmer on the go." name = "automat" +version = "20.2.0" +description = "Self-service finite-state machines for the programmer on the go." +category = "main" optional = false python-versions = "*" -version = "20.2.0" [package.dependencies] attrs = ">=19.2.0" @@ -121,32 +119,32 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -category = "main" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" name = "autopep8" +version = "1.5.4" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "main" optional = false python-versions = "*" -version = "1.5.4" [package.dependencies] pycodestyle = ">=2.6.0" toml = "*" [[package]] -category = "dev" -description = "Specifications for callback functions passed in to an API" name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "20.8b1" [package.dependencies] appdirs = "*" @@ -163,12 +161,12 @@ colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "The AWS SDK for Python" name = "boto3" +version = "1.16.43" +description = "The AWS SDK for Python" +category = "main" optional = false python-versions = "*" -version = "1.16.43" [package.dependencies] botocore = ">=1.19.43,<1.20.0" @@ -176,28 +174,25 @@ jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" [[package]] -category = "main" -description = "Low-level, data-driven core of boto 3." name = "botocore" +version = "1.19.43" +description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = "*" -version = "1.19.43" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" python-dateutil = ">=2.1,<3.0.0" - -[package.dependencies.urllib3] -python = "<3.4.0 || >=3.5.0" -version = ">=1.25.4,<1.27" +urllib3 = {version = ">=1.25.4,<1.27", markers = "python_version != \"3.4\""} [[package]] -category = "main" -description = "Minimal persistent memoization cache" name = "cachalot" +version = "1.5.0" +description = "Minimal persistent memoization cache" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "1.5.0" [package.dependencies] jsonpickle = "*" @@ -205,47 +200,47 @@ tinydb = ">=4,<5" tinydb-smartcache = "*" [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.12.5" [[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." name = "cffi" +version = "1.14.4" +description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" -version = "1.14.4" [package.dependencies] pycparser = "*" [[package]] -category = "main" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." name = "channels" +version = "2.4.0" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] -Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" +Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -category = "main" -description = "Redis-backed ASGI channel layer implementation" name = "channels-redis" +version = "2.4.1" +description = "Redis-backed ASGI channel layer implementation" +category = "main" optional = false python-versions = ">=3.6" -version = "2.4.1" [package.dependencies] aioredis = ">=1.0,<2.0" @@ -258,45 +253,44 @@ cryptography = ["cryptography (>=1.3.0)"] tests = ["cryptography (>=1.3.0)", "pytest (>=3.6.0,<3.7.0)", "pytest-asyncio (>=0.8,<1.0)", "async-generator (>=1.8,<2.0)", "async-timeout (>=2.0,<3.0)"] [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.0.0" [[package]] -category = "dev" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" [[package]] -category = "main" -description = "Symbolic constants in Python" name = "constantly" +version = "15.1.0" +description = "Symbolic constants in Python" +category = "main" optional = false python-versions = "*" -version = "15.1.0" [[package]] -category = "dev" -description = "Python client library for Core API." name = "coreapi" +version = "2.3.3" +description = "Python client library for Core API." +category = "dev" optional = false python-versions = "*" -version = "2.3.3" [package.dependencies] coreschema = "*" @@ -305,80 +299,80 @@ requests = "*" uritemplate = "*" [[package]] -category = "dev" -description = "Core Schema." name = "coreschema" +version = "0.0.4" +description = "Core Schema." +category = "dev" optional = false python-versions = "*" -version = "0.0.4" [package.dependencies] jinja2 = "*" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3.1" +description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.3.1" + +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["toml"] [[package]] -category = "main" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." name = "cryptography" +version = "3.3.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" -version = "3.3.1" [package.dependencies] cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -category = "main" -description = "Django ASGI (HTTP/WebSocket) server" name = "daphne" +version = "2.5.0" +description = "Django ASGI (HTTP/WebSocket) server" +category = "main" optional = false python-versions = "*" -version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" - -[package.dependencies.twisted] -extras = ["tls"] -version = ">=18.7" +twisted = {version = ">=18.7", extras = ["tls"]} [package.extras] -tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] [[package]] -category = "dev" -description = "Decorators for Humans" name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.4.2" [[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" +version = "3.1.4" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.6" -version = "3.1.4" [package.dependencies] asgiref = ">=3.2.10,<4" @@ -390,99 +384,99 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -category = "main" -description = "Caches your Django ORM queries and automatically invalidates them." name = "django-cachalot" +version = "2.3.5" +description = "Caches your Django ORM queries and automatically invalidates them." +category = "main" optional = false python-versions = "*" -version = "2.3.5" [package.dependencies] Django = ">=2" [[package]] -category = "main" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." name = "django-cors-headers" +version = "3.2.1" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +category = "main" optional = false python-versions = ">=3.5" -version = "3.2.1" [package.dependencies] Django = ">=1.11" [[package]] -category = "main" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." name = "django-filter" +version = "2.4.0" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "Django middlewares to monitor your application with Prometheus.io." name = "django-prometheus" +version = "2.1.0" +description = "Django middlewares to monitor your application with Prometheus.io." +category = "main" optional = false python-versions = "*" -version = "2.1.0" [package.dependencies] prometheus-client = ">=0.7" [[package]] -category = "main" -description = "Full featured redis cache backend for Django." name = "django-redis" +version = "4.11.0" +description = "Full featured redis cache backend for Django." +category = "main" optional = false python-versions = ">=3.5" -version = "4.11.0" [package.dependencies] Django = ">=1.11" redis = ">=2.10.0" [[package]] -category = "main" -description = "Redis Cache Backend for Django" name = "django-redis-cache" +version = "2.1.1" +description = "Redis Cache Backend for Django" +category = "main" optional = false python-versions = "*" -version = "2.1.1" [package.dependencies] redis = "<4.0" six = "*" [[package]] -category = "main" -description = "Silky smooth profiling for the Django Framework" name = "django-silk" +version = "4.1.0" +description = "Silky smooth profiling for the Django Framework" +category = "main" optional = false python-versions = ">=3.5" -version = "4.1.0" [package.dependencies] +autopep8 = "*" Django = ">=2.2" +gprof2dot = ">=2017.09.19" Jinja2 = "*" Pygments = "*" -autopep8 = "*" -gprof2dot = ">=2017.09.19" python-dateutil = "*" pytz = "*" requests = "*" sqlparse = "*" [[package]] -category = "main" -description = "Support for many storage backends in Django" name = "django-storages" +version = "1.9.1" +description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.1" [package.dependencies] Django = ">=1.11" @@ -496,12 +490,12 @@ libcloud = ["apache-libcloud"] sftp = ["paramiko"] [[package]] -category = "dev" -description = "Mypy stubs for Django" name = "django-stubs" +version = "1.7.0" +description = "Mypy stubs for Django" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.7.0" [package.dependencies] django = "*" @@ -509,35 +503,35 @@ mypy = ">=0.790" typing-extensions = "*" [[package]] -category = "main" -description = "A translatable password validator for django, based on zxcvbn-python." name = "django-zxcvbn-password-validator" +version = "1.3.2" +description = "A translatable password validator for django, based on zxcvbn-python." +category = "main" optional = false python-versions = "*" -version = "1.3.0" [package.dependencies] Django = ">=2.0" zxcvbn = "*" [[package]] -category = "main" -description = "Web APIs for Django, made easy." name = "djangorestframework" +version = "3.11.1" +description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.5" -version = "3.11.1" [package.dependencies] django = ">=1.11" [[package]] -category = "dev" -description = "PEP-484 stubs for django-rest-framework" name = "djangorestframework-stubs" +version = "1.3.0" +description = "PEP-484 stubs for django-rest-framework" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.3.0" [package.dependencies] coreapi = ">=2.0.0" @@ -547,23 +541,20 @@ requests = ">=2.0.0" typing-extensions = ">=3.7.2" [[package]] -category = "main" -description = "Generate a dot graph from the output of several profilers." name = "gprof2dot" +version = "2019.11.30" +description = "Generate a dot graph from the output of several profilers." +category = "main" optional = false python-versions = "*" -version = "2019.11.30" [[package]] -category = "main" -description = "WSGI HTTP Server for UNIX" name = "gunicorn" +version = "20.0.4" +description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.4" -version = "20.0.4" - -[package.dependencies] -setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.9.7)"] @@ -572,70 +563,69 @@ setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] [[package]] -category = "main" -description = "Python wrapper for hiredis" name = "hiredis" +version = "1.1.0" +description = "Python wrapper for hiredis" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" [[package]] -category = "main" -description = "A featureful, immutable, and correct URL for Python." name = "hyperlink" +version = "20.0.1" +description = "A featureful, immutable, and correct URL for Python." +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.0.1" [package.dependencies] idna = ">=2.5" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "main" -description = "" name = "incremental" +version = "17.5.0" +description = "" +category = "main" optional = false python-versions = "*" -version = "17.5.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] -category = "dev" -description = "iniconfig: brain-dead simple config-ini parsing" name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = "*" -version = "1.1.1" [[package]] -category = "dev" -description = "IPython: Productive Interactive Computing" name = "ipython" +version = "7.19.0" +description = "IPython: Productive Interactive Computing" +category = "dev" optional = false python-versions = ">=3.7" -version = "7.19.0" [package.dependencies] -appnope = "*" +appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.10" -pexpect = ">4.3" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" -setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -650,43 +640,43 @@ qtconsole = ["qtconsole"] test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] [[package]] -category = "dev" -description = "Vestigial utilities from IPython" name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "Simple immutable types for python." name = "itypes" +version = "1.2.0" +description = "Simple immutable types for python." +category = "dev" optional = false python-versions = "*" -version = "1.2.0" [[package]] -category = "dev" -description = "An autocompletion tool for Python that can be used for text editors." name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" -version = "0.18.0" [package.dependencies] parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.11.2" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -695,49 +685,49 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "JSON Matching Expressions" name = "jmespath" +version = "0.10.0" +description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.0" [[package]] -category = "main" -description = "Python library for serializing any arbitrary object graph into JSON" name = "jsonpickle" +version = "2.0.0" +description = "Python library for serializing any arbitrary object graph into JSON" +category = "main" optional = false python-versions = ">=2.7" -version = "2.0.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] +testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] "testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] -category = "main" -description = "MessagePack (de)serializer." name = "msgpack" +version = "0.6.2" +description = "MessagePack (de)serializer." +category = "main" optional = false python-versions = "*" -version = "0.6.2" [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.790" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -748,191 +738,189 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "New Relic Python Agent" name = "newrelic" +version = "5.24.0.153" +description = "New Relic Python Agent" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "5.24.0.153" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.9" [package.dependencies] pyparsing = ">=2.0.2" [[package]] -category = "dev" -description = "A Python Parser" name = "parso" +version = "0.8.1" +description = "A Python Parser" +category = "dev" optional = false python-versions = ">=3.6" -version = "0.8.1" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.1" [[package]] -category = "dev" -description = "Pexpect allows easy control of interactive console applications." -marker = "sys_platform != \"win32\"" name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" -version = "4.8.0" [package.dependencies] ptyprocess = ">=0.5" [[package]] -category = "dev" -description = "Tiny 'shelve'-like database with concurrency support" name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" optional = false python-versions = "*" -version = "0.7.5" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "main" -description = "Python client for the Prometheus monitoring system." name = "prometheus-client" +version = "0.9.0" +description = "Python client for the Prometheus monitoring system." +category = "main" optional = false python-versions = "*" -version = "0.9.0" [package.extras] twisted = ["twisted"] [[package]] -category = "dev" -description = "Library for building powerful interactive command lines in Python" name = "prompt-toolkit" +version = "3.0.8" +description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.6.1" -version = "3.0.8" [package.dependencies] wcwidth = "*" [[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" name = "psycopg2-binary" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "2.8.6" [[package]] -category = "dev" -description = "Run a subprocess in a pseudo terminal" -marker = "sys_platform != \"win32\"" name = "ptyprocess" +version = "0.6.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" -version = "0.6.0" [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.10.0" [[package]] -category = "main" -description = "ASN.1 types and codecs" name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" optional = false python-versions = "*" -version = "0.4.8" [[package]] -category = "main" -description = "A collection of ASN.1-based protocols modules." name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "main" optional = false python-versions = "*" -version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -category = "main" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "main" -description = "C parser in Python" name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" [[package]] -category = "dev" -description = "passive checker of Python programs" name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.3.1" [[package]] -category = "main" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.3" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.5" -version = "2.7.3" [[package]] -category = "main" -description = "Python wrapper module around the OpenSSL library" name = "pyopenssl" +version = "20.0.1" +description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "20.0.1" [package.dependencies] cryptography = ">=3.2" @@ -943,33 +931,33 @@ docs = ["sphinx", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] -category = "main" -description = "Python One Time Password Library" name = "pyotp" +version = "2.3.0" +description = "Python One Time Password Library" +category = "main" optional = false python-versions = "*" -version = "2.3.0" [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.6" -version = "6.2.4" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<1.0.0a1" @@ -980,30 +968,27 @@ toml = "*" testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "dev" -description = "Pytest plugin for measuring coverage." name = "pytest-cov" +version = "2.12.0" +description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.12.0" [package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -[package.dependencies.coverage] -extras = ["toml"] -version = ">=5.2.1" - [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] -category = "dev" -description = "A Django plugin for pytest." name = "pytest-django" +version = "4.3.0" +description = "A Django plugin for pytest." +category = "dev" optional = false python-versions = ">=3.5" -version = "4.3.0" [package.dependencies] pytest = ">=5.4.0" @@ -1013,66 +998,66 @@ docs = ["sphinx", "sphinx-rtd-theme"] testing = ["django", "django-configurations (>=2.0)"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "HTTP REST client, simplified for Python" name = "python-http-client" +version = "3.3.1" +description = "HTTP REST client, simplified for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.3.1" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.5" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2020.5" [[package]] -category = "dev" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "5.4.1" [[package]] -category = "main" -description = "Python client for Redis key-value store" name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.11.13" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.25.1" [package.dependencies] certifi = ">=2017.4.17" @@ -1082,38 +1067,38 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "main" -description = "An Amazon S3 Transfer Manager" name = "s3transfer" +version = "0.3.3" +description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = "*" -version = "0.3.3" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [[package]] -category = "main" -description = "Twilio SendGrid library for Python" name = "sendgrid" +version = "6.4.8" +description = "Twilio SendGrid library for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "6.4.8" [package.dependencies] python-http-client = ">=3.2.1" starkbank-ecdsa = ">=1.0.0" [[package]] -category = "main" -description = "Python client for Sentry (https://sentry.io)" name = "sentry-sdk" +version = "0.16.5" +description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" -version = "0.16.5" [package.dependencies] certifi = "*" @@ -1135,23 +1120,23 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] [[package]] -category = "main" -description = "ridiculously fast object serialization" name = "serpy" +version = "0.3.1" +description = "ridiculously fast object serialization" +category = "main" optional = false python-versions = "*" -version = "0.3.1" [package.dependencies] six = "*" [[package]] -category = "main" -description = "Service identity verification for pyOpenSSL & cryptography." name = "service-identity" +version = "18.1.0" +description = "Service identity verification for pyOpenSSL & cryptography." +category = "main" optional = false python-versions = "*" -version = "18.1.0" [package.dependencies] attrs = ">=16.0.0" @@ -1166,63 +1151,63 @@ idna = ["idna"] tests = ["coverage (>=4.2.0)", "pytest"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "main" -description = "A non-validating SQL parser." name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" optional = false python-versions = ">=3.5" -version = "0.4.1" [[package]] -category = "main" -description = "A lightweight and fast pure python ECDSA library" name = "starkbank-ecdsa" +version = "1.1.0" +description = "A lightweight and fast pure python ECDSA library" +category = "main" optional = false python-versions = "*" -version = "1.1.0" [[package]] -category = "main" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" name = "tinydb" +version = "4.4.0" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "4.4.0" [[package]] -category = "main" -description = "A smarter query cache for TinyDB" name = "tinydb-smartcache" +version = "2.0.0" +description = "A smarter query cache for TinyDB" +category = "main" optional = false python-versions = ">=3.5,<4.0" -version = "2.0.0" [package.dependencies] tinydb = ">=4.0,<5.0" [[package]] -category = "main" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.2" [[package]] -category = "dev" -description = "Traitlets Python configuration system" name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.7" -version = "5.0.5" [package.dependencies] ipython-genutils = "*" @@ -1231,34 +1216,25 @@ ipython-genutils = "*" test = ["pytest"] [[package]] -category = "main" -description = "An asynchronous networking framework written in Python" name = "twisted" +version = "21.2.0" +description = "An asynchronous networking framework written in Python" +category = "main" optional = false python-versions = ">=3.5.4" -version = "21.2.0" [package.dependencies] -Automat = ">=0.8.0" attrs = ">=19.2.0" +Automat = ">=0.8.0" constantly = ">=15.1" hyperlink = ">=17.1.1" +idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" -twisted-iocpsupport = ">=1.0.0,<1.1.0" +pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} +service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} +twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} "zope.interface" = ">=4.4.2" -[package.dependencies.idna] -optional = true -version = ">=2.4" - -[package.dependencies.pyopenssl] -optional = true -version = ">=16.0.0" - -[package.dependencies.service-identity] -optional = true -version = ">=18.1.0" - [package.extras] all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] @@ -1274,82 +1250,78 @@ tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] [[package]] -category = "main" -description = "An extension for use in the twisted I/O Completion Ports reactor." -marker = "platform_system == \"Windows\"" name = "twisted-iocpsupport" +version = "1.0.1" +description = "An extension for use in the twisted I/O Completion Ports reactor." +category = "main" optional = false python-versions = "*" -version = "1.0.1" [[package]] -category = "main" -description = "Compatibility API between asyncio/Twisted/Trollius" name = "txaio" +version = "20.12.1" +description = "Compatibility API between asyncio/Twisted/Trollius" +category = "main" optional = false python-versions = ">=3.6" -version = "20.12.1" [package.extras] all = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] twisted = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false python-versions = "*" -version = "3.7.4.3" [[package]] -category = "dev" -description = "URI templates" name = "uritemplate" +version = "3.0.1" +description = "URI templates" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.1" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.2" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" -version = "0.2.5" [[package]] -category = "main" -description = "Interfaces for Python" name = "zope.interface" +version = "5.2.0" +description = "Interfaces for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.2.0" - -[package.dependencies] -setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1357,17 +1329,17 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [[package]] -category = "main" -description = "" name = "zxcvbn" +version = "4.4.28" +description = "" +category = "main" optional = false python-versions = "*" -version = "4.4.28" [metadata] -content-hash = "63be3bb11534daba55f8cd13d01c6ac94f1ca5fd00176ea0df1f04c2cd3119b3" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.9" +content-hash = "226b334134372a933b21b2fbef2fd5765accbe3586679b27c0a0c29589641b05" [metadata.files] aioredis = [ @@ -1621,7 +1593,8 @@ django-stubs = [ {file = "django_stubs-1.7.0-py3-none-any.whl", hash = "sha256:30a7d99c694acf79c5d93d69a5a8e4b54d2a8c11dd672aa869006789e2189fa6"}, ] django-zxcvbn-password-validator = [ - {file = "django-zxcvbn-password-validator-1.3.0.tar.gz", hash = "sha256:a3435aec55573d28d6b000cb751cf0f2dc2fc3af2ad72d4cea13eb54b3740612"}, + {file = "django-zxcvbn-password-validator-1.3.2.tar.gz", hash = "sha256:5df0eec9515713e51aefb8fcc99c6921b9b3561cb4c290c1d0107d60c0a43242"}, + {file = "django_zxcvbn_password_validator-1.3.2-py3-none-any.whl", hash = "sha256:d063d9c563e5d6a165b9d7641305e062110da85706fb7189d7ce17b50b5ebfb8"}, ] djangorestframework = [ {file = "djangorestframework-3.11.1-py3-none-any.whl", hash = "sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b"}, @@ -1749,20 +1722,39 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] msgpack = [ @@ -1886,8 +1878,11 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, ] ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, diff --git a/pyproject.toml b/pyproject.toml index 44548bde..e50c7642 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ django-cors-headers = "3.2.1" django-redis = "4.11.0" django-redis-cache = "2.1.1" django-storages = "1.9.1" -django-zxcvbn-password-validator = "1.3.0" djangorestframework = "3.11.1" pyotp = "2.3.0" sentry-sdk = "^0.16.3" @@ -27,6 +26,7 @@ django-cachalot = "^2.3.5" cachalot = "^1.5.0" django-silk = "^4.1.0" serpy = "^0.3.1" +django-zxcvbn-password-validator = "^1.3.2" [tool.poetry.dev-dependencies] ipython = "^7.19.0" From 07d54ee487c10ee30f838bbf4c998e84a58c2c02 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Thu, 3 Jun 2021 14:29:31 +0100 Subject: [PATCH 043/185] Start work on removing challenge test order requirement --- src/challenge/tests/mixins.py | 8 ++++++++ src/challenge/tests/test_views.py | 7 +++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index b4924500..d4362984 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -1,3 +1,5 @@ +from typing import Optional + from challenge.models import Category, Challenge from hint.models import Hint from member.models import Member @@ -63,3 +65,9 @@ def setUp(self) -> None: self.team2 = Team.objects.create(name="team2", password="password", owner=self.user3) self.user3.team = self.team2 self.user3.save() + + def get_json_for(self, challenge: "Challenge", data: dict[str, list[dict]]) -> Optional[dict]: + """Get the relevant serialized JSON for a specicifed challenge.""" + for serialized_challenge in data.get("d", [{}])[0].get("challenges", ()): + if serialized_challenge.get("id") == challenge.pk: + return serialized_challenge diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 26293c8e..1635138c 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -175,22 +175,21 @@ def test_category_list_authenticated_content(self): def test_category_list_challenge_redacting(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse("description" in response.data["d"][0]["challenges"][0]) + self.assertFalse("description" in self.get_json_for(self.challenge1, data=response.data)) def test_category_list_challenge_redacting_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertTrue("description" in response.data["d"][0]["challenges"][0]) + self.assertFalse("description" in self.get_json_for(self.challenge3, data=response.data)) def test_category_list_challenge_unlocked_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - print(response.data["d"]) - self.assertFalse(response.data["d"][0]["challenges"][0]["unlocked"]) + self.assertFalse(self.get_json_for(self.challenge1, data=response.data)) def test_category_create(self): self.user.is_staff = True From 8d751b5c786c63b5e3792172874c4b10c5764525 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 01:34:52 +0100 Subject: [PATCH 044/185] load config defaults properly --- src/config/backends.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config/backends.py b/src/config/backends.py index ef2c21e1..0a33398c 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -58,6 +58,7 @@ def get(self, key): value = self.cache.get(f'config_{key}') if value is None: return None + print(value) return pickle.loads(value) def set(self, key, value): @@ -77,7 +78,7 @@ def get_all(self): return config def set_if_not_exists(self, key, value): - if self.cache.add('config_' + key, value, timeout=None): + if self.cache.add('config_' + key, pickle.dumps(value), timeout=None): self.keys.add(f"config_{key}") def load(self, defaults): From ba069f965acc86b03b2b881c319770d798c67668 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 01:35:35 +0100 Subject: [PATCH 045/185] remove debug thing --- src/config/backends.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config/backends.py b/src/config/backends.py index 0a33398c..feddebd8 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -58,7 +58,6 @@ def get(self, key): value = self.cache.get(f'config_{key}') if value is None: return None - print(value) return pickle.loads(value) def set(self, key, value): From 6e27b8a2032e2e95dd0a534fba8d7c74bcbdece5 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 02:16:35 +0100 Subject: [PATCH 046/185] extract common code out of serializers --- src/challenge/serializers.py | 62 +++++++++++++----------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index c6db9114..b95d1568 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -8,6 +8,21 @@ from hint.serializers import HintSerializer, FastHintSerializer +def setup_context(context): + context.update({ + "request": context["request"], + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), + }) + if context["request"].user.team is not None: + context.update({ + "solves": list( + context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) + ), + }) + + class ForeignKeyField(serpy.Field): """A :class:`Field` that gets a given attribute from a foreign object.""" def __init__(self, *args, attr_name="id", **kwargs): @@ -116,16 +131,7 @@ def __init__(self, *args, **kwargs): if 'context' in kwargs: self.context = kwargs['context'] if 'solve_counter' not in self.context: - self.context.update({ - "request": self.context["request"], - "solves": list( - self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", - flat=True) - ), - "solve_counter": get_solve_counts(), - "votes_positive_counter": get_positive_votes(), - "votes_negative_counter": get_negative_votes(), - }) + setup_context(self.context) def serialize(self, instance): if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ @@ -152,15 +158,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs: self.context = kwargs["context"] - self.context.update({ - "request": self.context["request"], - "solves": list( - self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ), - "solve_counter": get_solve_counts(), - "votes_positive_counter": get_positive_votes(), - "votes_negative_counter": get_negative_votes(), - }) + setup_context(self.context) def get_challenges(self, instance): return FastChallengeSerializer(instance.challenges, many=True, context=self.context).data @@ -208,20 +206,12 @@ def __init__(self, *args, **kwargs): super(FastAdminChallengeSerializer, self).__init__(*args, **kwargs) if 'context' in kwargs: self.context = kwargs['context'] - if 'nested' not in kwargs: - self.context.update({ - "request": self.context["request"], - "solves": list( - self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", - flat=True) - ), - "solve_counter": get_solve_counts(), - "votes_positive_counter": get_positive_votes(), - "votes_negative_counter": get_negative_votes(), - }) + if 'solve_counter' not in self.context: + setup_context(self.context) def serialize(self, instance): - return super(FastAdminChallengeSerializer, FastAdminChallengeSerializer(instance, context=self.context)).to_value(instance) + return super(FastAdminChallengeSerializer, FastAdminChallengeSerializer(instance, context=self.context))\ + .to_value(instance) def to_value(self, instance): if self.many: @@ -295,15 +285,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs: self.context = kwargs["context"] - self.context.update({ - "request": self.context["request"], - "solves": list( - self.context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ), - "solve_counter": get_solve_counts(), - "votes_positive_counter": get_positive_votes(), - "votes_negative_counter": get_negative_votes(), - }) + setup_context(self.context) def get_challenges(self, instance): return FastAdminChallengeSerializer(instance.challenges, many=True, context=self.context).data From dbbd7fca8cd95c7883cf9a6981887e51b7332a50 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 02:37:31 +0100 Subject: [PATCH 047/185] cache invalidation --- src/challenge/apps.py | 4 ++++ src/challenge/signals.py | 11 +++++++++++ src/challenge/views.py | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/challenge/signals.py diff --git a/src/challenge/apps.py b/src/challenge/apps.py index 6c2ea3cb..5f0a0984 100644 --- a/src/challenge/apps.py +++ b/src/challenge/apps.py @@ -3,3 +3,7 @@ class ChallengeConfig(AppConfig): name = "challenge" + + def ready(self): + # noinspection PyUnresolvedReferences + import challenge.signals diff --git a/src/challenge/signals.py b/src/challenge/signals.py new file mode 100644 index 00000000..81b1c4b4 --- /dev/null +++ b/src/challenge/signals.py @@ -0,0 +1,11 @@ +from django.core.cache import caches +from django.db.models.signals import post_save +from django.dispatch import receiver + +from challenge.models import Challenge + + +@receiver(post_save, sender=Challenge) +def challenge_save(sender, instance, **kwargs): + new_index = caches['default'].get('challenge_mod_index', 0) + 1 + caches['default'].set('challenge_mod_index', new_index) diff --git a/src/challenge/views.py b/src/challenge/views.py index ad9dfa54..d39316f3 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -39,9 +39,9 @@ def get_cache_key(user): if user.team is None: - return 'categoryvs_no_team' + return str(caches['default'].get('challenge_mod_index', 0)) + 'categoryvs_no_team' else: - return 'categoryvs_team_' + str(user.team.id) + return str(caches['default'].get('challenge_mod_index', 0)) + 'categoryvs_team_' + str(user.team.id) class CategoryViewset(AdminCreateModelViewSet): From db67d55d1bc844d6814ce7e8095f72d9fcae176e Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 02:43:27 +0100 Subject: [PATCH 048/185] delete user if email sending fails --- src/authentication/serializers.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index be0bebb4..287cebb0 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -59,18 +59,12 @@ def create(self, validated_data): user.is_staff = True user.is_superuser = True + invite_code = None if config.config.get("invite_required"): if InviteCode.objects.filter(code=validated_data["invite"]): - code = InviteCode.objects.get(code=validated_data["invite"]) - if code: - if code.uses >= code.max_uses: - raise FormattedException(m="invite_already_used", status=HTTP_403_FORBIDDEN) - code.uses += 1 - if code.uses >= code.max_uses: - code.fully_used = True - code.save() - if code.auto_team: - user.team = code.auto_team + invite_code = InviteCode.objects.get(code=validated_data["invite"]) + if invite_code.uses >= invite_code.max_uses: + raise FormattedException(m="invite_already_used", status=HTTP_403_FORBIDDEN) else: raise FormattedException(m="invalid_invite", status=HTTP_403_FORBIDDEN) @@ -79,8 +73,20 @@ def create(self, validated_data): user.is_visible = True else: user.save() - send_email(user.email, 'RACTF - Verify your email', 'verify', - url=settings.FRONTEND_URL + 'verify?id={}&secret={}'.format(user.id, user.email_token)) + try: + send_email(user.email, 'RACTF - Verify your email', 'verify', + url=settings.FRONTEND_URL + 'verify?id={}&secret={}'.format(user.id, user.email_token)) + except: + user.delete() + raise FormattedException(m="creation_failed") + + if invite_code: + invite_code.uses += 1 + if invite_code.uses >= invite_code.max_uses: + invite_code.fully_used = True + invite_code.save() + if invite_code.auto_team: + user.team = invite_code.auto_team if not config.config.get("enable_teams"): user.save() From 40d66c4482a1a21bc49f97afd7fb727e504df371 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 02:48:17 +0100 Subject: [PATCH 049/185] make silk an environment variable --- src/backend/settings/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index d371aeed..d1259f04 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -73,7 +73,6 @@ "storages", "corsheaders", "cachalot", - #"silk", "django_prometheus", "django.contrib.auth", "django.contrib.contenttypes", @@ -92,11 +91,14 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - #'silk.middleware.SilkyMiddleware', "django_prometheus.middleware.PrometheusAfterMiddleware", ] -#SILKY_PYTHON_PROFILER = True +if os.getenv("ENABLE_SILK"): + INSTALLED_APPS.insert(len(INSTALLED_APPS) - 6, "silk") + MIDDLEWARE.insert(len(MIDDLEWARE) - 2, 'silk.middleware.SilkyMiddleware') + SILKY_PYTHON_PROFILER = True + ROOT_URLCONF = "backend.urls" From 701d80dcdfa0799c2ebc75aa5c559b899591bedc Mon Sep 17 00:00:00 2001 From: David Date: Fri, 4 Jun 2021 02:51:38 +0100 Subject: [PATCH 050/185] move config defaults --- src/backend/settings/__init__.py | 38 +++++++++++++++++++++++++++++ src/config/config.py | 42 ++------------------------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index d1259f04..ef9a2d1c 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -3,6 +3,7 @@ # flake8: noqa import os +import time from pathlib import Path from corsheaders.defaults import default_headers @@ -48,6 +49,43 @@ MEDIA_URL = "/publicmedia/" MEDIA_ROOT = os.path.join(BASE_DIR, "publicmedia") +DEFAULT_CONFIG = { + 'config_version': 5, + 'flag_prefix': 'ractf', + 'graph_members': 10, + 'register_start_time': time.time(), + 'register_end_time': -1, + 'end_time': time.time() + 7 * 24 * 60 * 60, + 'start_time': time.time(), + 'team_size': -1, + 'email_allow': "a", + 'login_provider': 'basic_auth', + 'registration_provider': 'basic_auth', + 'token_provider': 'basic_auth', + 'enable_bot_users': True, + 'enable_caching': True, + 'enable_ctftime': True, + 'enable_flag_submission': True, + 'enable_flag_submission_after_competition': True, + 'enable_force_admin_2fa': False, + 'enable_track_incorrect_submissions': True, + 'enable_login': True, + 'enable_prelogin': True, + 'enable_maintenance_mode': False, + 'enable_registration': True, + 'enable_scoreboard': True, + 'enable_scoring': True, + 'enable_solve_broadcast': True, + 'enable_teams': True, + 'enable_team_join': True, + 'enable_view_challenges_after_competion': True, + 'enable_team_leave': False, + 'invite_required': False, + 'hide_scoreboard_at': -1, + 'setup_wizard_complete': False, + 'sensitive_fields': ['sensitive_fields', 'enable_force_admin_2fa'] +} + INSTALLED_APPS = [ "announcements.apps.AnnouncementsConfig", "authentication.apps.AuthConfig", diff --git a/src/config/config.py b/src/config/config.py index c02c3480..9c2335f5 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -1,47 +1,9 @@ -import time from pydoc import locate from django.conf import settings -DEFAULT_CONFIG = { - 'config_version': 5, - 'flag_prefix': 'ractf', - 'graph_members': 10, - 'register_start_time': time.time(), - 'register_end_time': -1, - 'end_time': time.time() + 7 * 24 * 60 * 60, - 'start_time': time.time(), - 'team_size': -1, - 'email_allow': "a", - 'login_provider': 'basic_auth', - 'registration_provider': 'basic_auth', - 'token_provider': 'basic_auth', - 'enable_bot_users': True, - 'enable_caching': True, - 'enable_ctftime': True, - 'enable_flag_submission': True, - 'enable_flag_submission_after_competition': True, - 'enable_force_admin_2fa': False, - 'enable_track_incorrect_submissions': True, - 'enable_login': True, - 'enable_prelogin': True, - 'enable_maintenance_mode': False, - 'enable_registration': True, - 'enable_scoreboard': True, - 'enable_scoring': True, - 'enable_solve_broadcast': True, - 'enable_teams': True, - 'enable_team_join': True, - 'enable_view_challenges_after_competion': True, - 'enable_team_leave': False, - 'invite_required': False, - 'hide_scoreboard_at': -1, - 'setup_wizard_complete': False, - 'sensitive_fields': ['sensitive_fields', 'enable_force_admin_2fa'] -} - backend = locate(settings.CONFIG['BACKEND'])() -backend.load(defaults=DEFAULT_CONFIG) +backend.load(defaults=settings.DEFAULT_CONFIG) def get(key): @@ -74,4 +36,4 @@ def set_bulk(values: dict): def add_plugin_config(name, config): - DEFAULT_CONFIG[name] = config + settings.DEFAULT_CONFIG[name] = config From 35c921b6f4ee6f00a913fd13a84642429a01f70f Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:02:39 +0100 Subject: [PATCH 051/185] Re-lock poetry lockfile --- poetry.lock | 1245 +++++++++++++++++---------------------------------- 1 file changed, 415 insertions(+), 830 deletions(-) diff --git a/poetry.lock b/poetry.lock index 92a94eeb..4f68fa53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,53 +1,51 @@ [[package]] -category = "main" -description = "asyncio (PEP 3156) Redis support" name = "aioredis" +version = "1.3.1" +description = "asyncio (PEP 3156) Redis support" +category = "main" optional = false python-versions = "*" -version = "1.3.1" [package.dependencies] async-timeout = "*" hiredis = "*" [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Disable App Nap on macOS >= 10.9" -marker = "sys_platform == \"darwin\"" name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" optional = false python-versions = "*" -version = "0.1.2" [[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" name = "asgiref" +version = "3.3.4" +description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.6" -version = "3.3.4" [package.extras] tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] -category = "main" -description = "Timeout context manager for asyncio programs" name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.5.3" -version = "3.0.1" [[package]] -<<<<<<< HEAD name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." @@ -57,35 +55,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" -description = "Classes Without Boilerplate" -||||||| 8b20521 -name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" -======= ->>>>>>> master category = "main" -description = "Classes Without Boilerplate" -marker = "sys_platform == \"linux\"" -name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "21.2.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] -category = "main" -description = "WebSocket client & server library, WAMP real-time framework" name = "autobahn" +version = "21.3.1" +description = "WebSocket client & server library, WAMP real-time framework" +category = "main" optional = false python-versions = ">=3.7" -version = "21.3.1" [package.dependencies] cryptography = ">=3.4.6" @@ -105,7 +93,6 @@ twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)", "attrs (>=20.3.0)"] xbr = ["xbr (>=21.2.1)", "cbor2 (>=5.2.0)", "zlmdb (>=21.2.1)", "twisted (>=20.3.0)", "web3 (>=5.16.0)", "jinja2 (>=2.11.3)", "rlp (>=2.0.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=5.1.0)", "eth-abi (>=2.1.1)", "mnemonic (>=0.19)", "base58 (>=2.1.0)", "ecdsa (>=0.16.1)", "py-multihash (>=2.0.1)"] [[package]] -<<<<<<< HEAD name = "autoflake" version = "1.4" description = "Removes unused imports and unused variables" @@ -120,19 +107,9 @@ pyflakes = ">=1.1.0" name = "automat" version = "20.2.0" description = "Self-service finite-state machines for the programmer on the go." -||||||| 8b20521 -name = "automat" -version = "20.2.0" -description = "Self-service finite-state machines for the programmer on the go." -======= ->>>>>>> master category = "main" -description = "Self-service finite-state machines for the programmer on the go." -marker = "sys_platform == \"linux\"" -name = "automat" optional = false python-versions = "*" -version = "20.2.0" [package.dependencies] attrs = ">=19.2.0" @@ -142,32 +119,32 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -category = "main" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" name = "autopep8" +version = "1.5.7" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "main" optional = false python-versions = "*" -version = "1.5.7" [package.dependencies] pycodestyle = ">=2.7.0" toml = "*" [[package]] -category = "dev" -description = "Specifications for callback functions passed in to an API" name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "20.8b1" [package.dependencies] appdirs = "*" @@ -184,25 +161,25 @@ colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "The AWS SDK for Python" name = "boto3" +version = "1.17.88" +description = "The AWS SDK for Python" +category = "main" optional = false python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "1.17.75" [package.dependencies] -botocore = ">=1.20.75,<1.21.0" +botocore = ">=1.20.88,<1.21.0" jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.4.0,<0.5.0" [[package]] -category = "main" -description = "Low-level, data-driven core of boto 3." name = "botocore" +version = "1.20.88" +description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "1.20.75" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" @@ -210,73 +187,50 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = ">=1.25.4,<1.27" [package.extras] -crt = ["awscrt (0.11.15)"] - -[[package]] -<<<<<<< HEAD -name = "cachalot" -version = "1.5.0" -description = "Minimal persistent memoization cache" -category = "main" -optional = false -python-versions = ">=3.5,<4.0" - -[package.dependencies] -jsonpickle = "*" -tinydb = ">=4,<5" -tinydb-smartcache = "*" +crt = ["awscrt (==0.11.15)"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." -||||||| 8b20521 -name = "certifi" -version = "2020.12.5" -description = "Python package for providing Mozilla's CA Bundle." -======= ->>>>>>> master category = "main" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" optional = false python-versions = "*" -version = "2020.12.5" [[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" -version = "1.14.5" [package.dependencies] pycparser = "*" [[package]] -category = "main" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." name = "channels" +version = "2.4.0" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] -Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" +Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -category = "main" -description = "Redis-backed ASGI channel layer implementation" name = "channels-redis" +version = "2.4.1" +description = "Redis-backed ASGI channel layer implementation" +category = "main" optional = false python-versions = ">=3.6" -version = "2.4.1" [package.dependencies] aioredis = ">=1.0,<2.0" @@ -289,38 +243,44 @@ cryptography = ["cryptography (>=1.3.0)"] tests = ["cryptography (>=1.3.0)", "pytest (>=3.6.0,<3.7.0)", "pytest-asyncio (>=0.8,<1.0)", "async-generator (>=1.8,<2.0)", "async-timeout (>=2.0,<3.0)"] [[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" category = "main" -description = "Composable command line interface toolkit" -name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" category = "main" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\" or sys_platform == \"win32\"" -name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" [[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." category = "main" -description = "Symbolic constants in Python" -marker = "sys_platform == \"linux\"" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] name = "constantly" +version = "15.1.0" +description = "Symbolic constants in Python" +category = "main" optional = false python-versions = "*" -version = "15.1.0" [[package]] -category = "dev" -description = "Python client library for Core API." name = "coreapi" +version = "2.3.3" +description = "Python client library for Core API." +category = "dev" optional = false python-versions = "*" -version = "2.3.3" [package.dependencies] coreschema = "*" @@ -329,83 +289,77 @@ requests = "*" uritemplate = "*" [[package]] -category = "dev" -description = "Core Schema." name = "coreschema" +version = "0.0.4" +description = "Core Schema." +category = "dev" optional = false python-versions = "*" -version = "0.0.4" [package.dependencies] jinja2 = "*" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.5" - -[package.dependencies] -toml = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["toml"] [[package]] -category = "main" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.6" -version = "3.4.7" [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -category = "main" -description = "Django ASGI (HTTP/WebSocket) server" name = "daphne" +version = "2.5.0" +description = "Django ASGI (HTTP/WebSocket) server" +category = "main" optional = false python-versions = "*" -version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" - -[package.dependencies.twisted] -extras = ["tls"] -version = ">=18.7" +twisted = {version = ">=18.7", extras = ["tls"]} [package.extras] -tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] [[package]] -category = "dev" -description = "Decorators for Humans" name = "decorator" +version = "5.0.9" +description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.0.9" [[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" +version = "3.2.4" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.6" -version = "3.2.3" [package.dependencies] asgiref = ">=3.3.2,<4" @@ -417,119 +371,99 @@ argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] [[package]] -<<<<<<< HEAD name = "django-cachalot" -version = "2.3.5" +version = "2.4.1" description = "Caches your Django ORM queries and automatically invalidates them." category = "main" optional = false python-versions = "*" [package.dependencies] -Django = ">=2" +Django = ">=2.2,<3.3" [[package]] name = "django-cors-headers" version = "3.2.1" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -||||||| 8b20521 -name = "django-cors-headers" -version = "3.2.1" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -======= ->>>>>>> master category = "main" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -name = "django-cors-headers" optional = false python-versions = ">=3.5" -version = "3.2.1" [package.dependencies] Django = ">=1.11" [[package]] -category = "main" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." name = "django-filter" +version = "2.4.0" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +category = "main" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "Django middlewares to monitor your application with Prometheus.io." name = "django-prometheus" +version = "2.1.0" +description = "Django middlewares to monitor your application with Prometheus.io." +category = "main" optional = false python-versions = "*" -version = "2.1.0" [package.dependencies] prometheus-client = ">=0.7" [[package]] -category = "main" -description = "Full featured redis cache backend for Django." name = "django-redis" +version = "4.11.0" +description = "Full featured redis cache backend for Django." +category = "main" optional = false python-versions = ">=3.5" -version = "4.11.0" [package.dependencies] Django = ">=1.11" redis = ">=2.10.0" [[package]] -category = "main" -description = "Redis Cache Backend for Django" name = "django-redis-cache" +version = "2.1.1" +description = "Redis Cache Backend for Django" +category = "main" optional = false python-versions = "*" -version = "2.1.1" [package.dependencies] redis = "<4.0" six = "*" [[package]] -<<<<<<< HEAD name = "django-silk" version = "4.1.0" description = "Silky smooth profiling for the Django Framework" -||||||| 8b20521 -name = "django-silk" -version = "4.0.1" -description = "Silky smooth profiling for the Django Framework" -======= ->>>>>>> master category = "main" -description = "Silky smooth profiling for the Django Framework" -name = "django-silk" optional = false python-versions = ">=3.5" -version = "4.0.1" [package.dependencies] +autopep8 = "*" Django = ">=2.2" +gprof2dot = ">=2017.09.19" Jinja2 = "*" Pygments = "*" -autopep8 = "*" -gprof2dot = ">=2017.09.19" python-dateutil = "*" pytz = "*" requests = "*" sqlparse = "*" [[package]] -category = "main" -description = "Support for many storage backends in Django" name = "django-storages" +version = "1.9.1" +description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.1" [package.dependencies] Django = ">=1.11" @@ -543,12 +477,12 @@ libcloud = ["apache-libcloud"] sftp = ["paramiko"] [[package]] -category = "dev" -description = "Mypy stubs for Django" name = "django-stubs" +version = "1.8.0" +description = "Mypy stubs for Django" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.8.0" [package.dependencies] django = "*" @@ -557,56 +491,46 @@ mypy = ">=0.790" typing-extensions = "*" [[package]] -<<<<<<< HEAD -name = "django-zxcvbn-password-validator" -version = "1.3.2" -description = "A translatable password validator for django, based on zxcvbn-python." -||||||| 8b20521 -name = "django-zxcvbn-password-validator" -version = "1.3.0" -description = "A translatable password validator for django, based on zxcvbn-python." -======= -category = "dev" -description = "Monkey-patching and extensions for django-stubs" name = "django-stubs-ext" +version = "0.2.0" +description = "Monkey-patching and extensions for django-stubs" +category = "dev" optional = false python-versions = ">=3.6" -version = "0.2.0" [package.dependencies] django = "*" [[package]] ->>>>>>> master -category = "main" -description = "A translatable password validator for django, based on zxcvbn-python." name = "django-zxcvbn-password-validator" +version = "1.3.2" +description = "A translatable password validator for django, based on zxcvbn-python." +category = "main" optional = false python-versions = "*" -version = "1.3.0" [package.dependencies] Django = ">=2.0" zxcvbn = "*" [[package]] -category = "main" -description = "Web APIs for Django, made easy." name = "djangorestframework" +version = "3.11.1" +description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.5" -version = "3.11.1" [package.dependencies] django = ">=1.11" [[package]] -category = "dev" -description = "PEP-484 stubs for django-rest-framework" name = "djangorestframework-stubs" +version = "1.4.0" +description = "PEP-484 stubs for django-rest-framework" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.4.0" [package.dependencies] coreapi = ">=2.0.0" @@ -616,23 +540,20 @@ requests = ">=2.0.0" typing-extensions = ">=3.7.2" [[package]] -category = "main" -description = "Generate a dot graph from the output of several profilers." name = "gprof2dot" +version = "2021.2.21" +description = "Generate a dot graph from the output of several profilers." +category = "main" optional = false python-versions = "*" -version = "2021.2.21" [[package]] -category = "main" -description = "WSGI HTTP Server for UNIX" name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.5" -version = "20.1.0" - -[package.dependencies] -setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.24.1)"] @@ -641,67 +562,63 @@ setproctitle = ["setproctitle"] tornado = ["tornado (>=0.2)"] [[package]] -category = "main" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.6" -version = "0.12.0" [[package]] -category = "main" -description = "Python wrapper for hiredis" name = "hiredis" +version = "2.0.0" +description = "Python wrapper for hiredis" +category = "main" optional = false python-versions = ">=3.6" -version = "2.0.0" [[package]] -category = "main" -description = "A collection of framework independent HTTP protocol utils." -marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" name = "httptools" +version = "0.1.2" +description = "A collection of framework independent HTTP protocol utils." +category = "main" optional = false python-versions = "*" -version = "0.1.2" [package.extras] -test = ["Cython (0.29.22)"] +test = ["Cython (==0.29.22)"] [[package]] -category = "main" -description = "A featureful, immutable, and correct URL for Python." name = "hyperlink" +version = "21.0.0" +description = "A featureful, immutable, and correct URL for Python." +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "21.0.0" [package.dependencies] idna = ">=2.5" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -marker = "sys_platform == \"linux\"" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false -python-versions = ">=3.4" -version = "3.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -category = "main" -description = "A small library that versions your Python projects." -marker = "sys_platform == \"linux\"" name = "incremental" +version = "21.3.0" +description = "A small library that versions your Python projects." +category = "main" optional = false python-versions = "*" -version = "21.3.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] -<<<<<<< HEAD name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" @@ -711,37 +628,27 @@ python-versions = "*" [[package]] name = "ipython" -version = "7.19.0" -description = "IPython: Productive Interactive Computing" -||||||| 8b20521 -name = "ipython" -version = "7.19.0" +version = "7.24.1" description = "IPython: Productive Interactive Computing" -======= ->>>>>>> master category = "dev" -description = "IPython: Productive Interactive Computing" -name = "ipython" optional = false python-versions = ">=3.7" -version = "7.23.1" [package.dependencies] -appnope = "*" +appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.16" matplotlib-inline = "*" -pexpect = ">4.3" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" -setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.16)", "pygments", "qtconsole", "requests", "testpath"] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] @@ -749,46 +656,46 @@ nbformat = ["nbformat"] notebook = ["notebook", "ipywidgets"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.16)"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] [[package]] -category = "dev" -description = "Vestigial utilities from IPython" name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" optional = false python-versions = "*" -version = "0.2.0" [[package]] -category = "dev" -description = "Simple immutable types for python." name = "itypes" +version = "1.2.0" +description = "Simple immutable types for python." +category = "dev" optional = false python-versions = "*" -version = "1.2.0" [[package]] -category = "dev" -description = "An autocompletion tool for Python that can be used for text editors." name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" -version = "0.18.0" [package.dependencies] parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "3.0.1" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.6" -version = "3.0.1" [package.dependencies] MarkupSafe = ">=2.0" @@ -797,70 +704,47 @@ MarkupSafe = ">=2.0" i18n = ["Babel (>=2.7)"] [[package]] -category = "main" -description = "JSON Matching Expressions" name = "jmespath" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.10.0" - -[[package]] -<<<<<<< HEAD -name = "jsonpickle" -version = "2.0.0" -description = "Python library for serializing any arbitrary object graph into JSON" +description = "JSON Matching Expressions" category = "main" optional = false -python-versions = ">=2.7" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] -"testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "markupsafe" -version = "1.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -||||||| 8b20521 -name = "markupsafe" -version = "1.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." -======= ->>>>>>> master category = "main" -description = "Safely add untrusted strings to HTML/XML markup." -name = "markupsafe" optional = false python-versions = ">=3.6" -version = "2.0.1" [[package]] -category = "dev" -description = "Inline Matplotlib backend for Jupyter" name = "matplotlib-inline" +version = "0.1.2" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.1.2" [package.dependencies] traitlets = "*" [[package]] -category = "main" -description = "MessagePack (de)serializer." name = "msgpack" +version = "0.6.2" +description = "MessagePack (de)serializer." +category = "main" optional = false python-versions = "*" -version = "0.6.2" [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.812" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.812" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -871,26 +755,25 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "New Relic Python Agent" name = "newrelic" +version = "5.24.0.153" +description = "New Relic Python Agent" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "5.24.0.153" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] [[package]] -<<<<<<< HEAD name = "packaging" version = "20.9" description = "Core utilities for Python packages" @@ -903,55 +786,44 @@ pyparsing = ">=2.0.2" [[package]] name = "parso" -version = "0.8.1" -description = "A Python Parser" -||||||| 8b20521 -name = "parso" -version = "0.8.1" +version = "0.8.2" description = "A Python Parser" -======= ->>>>>>> master category = "dev" -description = "A Python Parser" -name = "parso" optional = false python-versions = ">=3.6" -version = "0.8.2" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.1" [[package]] -category = "dev" -description = "Pexpect allows easy control of interactive console applications." -marker = "sys_platform != \"win32\"" name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" -version = "4.8.0" [package.dependencies] ptyprocess = ">=0.5" [[package]] -category = "dev" -description = "Tiny 'shelve'-like database with concurrency support" name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" optional = false python-versions = "*" -version = "0.7.5" [[package]] -<<<<<<< HEAD name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" @@ -964,54 +836,43 @@ dev = ["pre-commit", "tox"] [[package]] name = "prometheus-client" -version = "0.9.0" +version = "0.11.0" description = "Python client for the Prometheus monitoring system." -||||||| 8b20521 -name = "prometheus-client" -version = "0.9.0" -description = "Python client for the Prometheus monitoring system." -======= ->>>>>>> master category = "main" -description = "Python client for the Prometheus monitoring system." -name = "prometheus-client" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.10.1" [package.extras] twisted = ["twisted"] [[package]] -category = "dev" -description = "Library for building powerful interactive command lines in Python" name = "prompt-toolkit" +version = "3.0.18" +description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.6.1" -version = "3.0.18" [package.dependencies] wcwidth = "*" [[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" name = "psycopg2-binary" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "2.8.6" [[package]] -category = "dev" -description = "Run a subprocess in a pseudo terminal" -marker = "sys_platform != \"win32\"" name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" -version = "0.7.0" [[package]] -<<<<<<< HEAD name = "py" version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" @@ -1031,37 +892,30 @@ python-versions = "*" name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." -||||||| 8b20521 -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" category = "main" optional = false python-versions = "*" +[package.dependencies] +pyasn1 = ">=0.4.6,<0.5.0" + [[package]] -name = "pyasn1-modules" -version = "0.2.8" -description = "A collection of ASN.1-based protocols modules." -======= ->>>>>>> master -category = "main" -description = "Python style guide checker" name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.7.0" [[package]] -category = "main" -description = "C parser in Python" name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" [[package]] -<<<<<<< HEAD name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" @@ -1071,40 +925,45 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.7.3" -description = "Pygments is a syntax highlighting package written in Python." -||||||| 8b20521 -name = "pygments" -version = "2.7.3" +version = "2.9.0" description = "Pygments is a syntax highlighting package written in Python." -======= ->>>>>>> master category = "main" -description = "Pygments is a syntax highlighting package written in Python." -name = "pygments" optional = false python-versions = ">=3.5" -version = "2.9.0" [[package]] -category = "main" -description = "Hamcrest framework for matcher objects" -marker = "sys_platform == \"linux\"" name = "pyhamcrest" +version = "2.0.2" +description = "Hamcrest framework for matcher objects" +category = "main" optional = false python-versions = ">=3.5" -version = "2.0.2" [[package]] +name = "pyopenssl" +version = "20.0.1" +description = "Python wrapper module around the OpenSSL library" category = "main" -description = "Python One Time Password Library" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.dependencies] +cryptography = ">=3.2" +six = ">=1.5.2" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] name = "pyotp" +version = "2.3.0" +description = "Python One Time Password Library" +category = "main" optional = false python-versions = "*" -version = "2.3.0" [[package]] -<<<<<<< HEAD name = "pyparsing" version = "2.4.7" description = "Python parsing module" @@ -1135,22 +994,23 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.12.0" +version = "2.12.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = ">=5.2.1" pytest = ">=4.6" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" -version = "4.3.0" +version = "4.4.0" description = "A Django plugin for pytest." category = "dev" optional = false @@ -1167,121 +1027,118 @@ testing = ["django", "django-configurations (>=2.0)"] name = "python-dateutil" version = "2.8.1" description = "Extensions to the standard Python datetime module" -||||||| 8b20521 -name = "python-dateutil" -version = "2.8.1" -description = "Extensions to the standard Python datetime module" -======= ->>>>>>> master category = "main" -description = "Extensions to the standard Python datetime module" -name = "python-dateutil" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "Read key-value pairs from a .env file and set them as environment variables" name = "python-dotenv" +version = "0.17.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" optional = false python-versions = "*" -version = "0.17.1" [package.extras] cli = ["click (>=5.0)"] [[package]] -category = "main" -description = "HTTP REST client, simplified for Python" name = "python-http-client" +version = "3.3.2" +description = "HTTP REST client, simplified for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.3.2" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2021.1" [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "5.4.1" [[package]] -category = "main" -description = "Python client for Redis key-value store" name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2021.4.4" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" optional = false -python-versions = "*" -version = "2.15.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "main" -description = "An Amazon S3 Transfer Manager" name = "s3transfer" +version = "0.4.2" +description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = "*" -version = "0.4.2" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [package.extras] -crt = ["botocore (>=1.20.29,<2.0a.0)"] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] [[package]] -category = "main" -description = "Twilio SendGrid library for Python" name = "sendgrid" +version = "6.7.0" +description = "Twilio SendGrid library for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "6.7.0" [package.dependencies] python-http-client = ">=3.2.1" starkbank-ecdsa = ">=1.0.0" [[package]] -category = "main" -description = "Python client for Sentry (https://sentry.io)" name = "sentry-sdk" +version = "1.1.0" +description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" -version = "1.1.0" [package.dependencies] certifi = "*" @@ -1304,7 +1161,6 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] tornado = ["tornado (>=5)"] [[package]] -<<<<<<< HEAD name = "serpy" version = "0.3.1" description = "ridiculously fast object serialization" @@ -1317,81 +1173,64 @@ six = "*" [[package]] name = "service-identity" -version = "18.1.0" -description = "Service identity verification for pyOpenSSL & cryptography." -||||||| 8b20521 -name = "service-identity" -version = "18.1.0" +version = "21.1.0" description = "Service identity verification for pyOpenSSL & cryptography." -======= ->>>>>>> master category = "main" -description = "Python 2 and 3 compatibility utilities" -name = "six" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.16.0" +python-versions = "*" -[[package]] -category = "main" -description = "A non-validating SQL parser." -name = "sqlparse" -optional = false -python-versions = ">=3.5" -version = "0.4.1" +[package.dependencies] +attrs = ">=19.1.0" +cryptography = "*" +pyasn1 = "*" +pyasn1-modules = "*" +six = "*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "pytest", "sphinx", "furo", "idna", "pyopenssl"] +docs = ["sphinx", "furo"] +idna = ["idna"] +tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" category = "main" -description = "A lightweight and fast pure python ECDSA library" -name = "starkbank-ecdsa" optional = false -python-versions = "*" -version = "1.1.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -<<<<<<< HEAD -name = "tinydb" -version = "4.4.0" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" +name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." category = "main" optional = false -python-versions = ">=3.5,<4.0" +python-versions = ">=3.5" [[package]] -name = "tinydb-smartcache" -version = "2.0.0" -description = "A smarter query cache for TinyDB" +name = "starkbank-ecdsa" +version = "1.1.0" +description = "A lightweight and fast pure python ECDSA library" category = "main" optional = false -python-versions = ">=3.5,<4.0" - -[package.dependencies] -tinydb = ">=4.0,<5.0" +python-versions = "*" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -||||||| 8b20521 -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -======= ->>>>>>> master category = "main" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.2" [[package]] -category = "dev" -description = "Traitlets Python configuration system" name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.7" -version = "5.0.5" [package.dependencies] ipython-genutils = "*" @@ -1400,135 +1239,116 @@ ipython-genutils = "*" test = ["pytest"] [[package]] -category = "main" -description = "An asynchronous networking framework written in Python" name = "twisted" +version = "20.3.0" +description = "An asynchronous networking framework written in Python" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "20.3.0" [package.dependencies] -Automat = ">=0.3.0" -PyHamcrest = ">=1.9.0,<1.10.0 || >1.10.0" attrs = ">=19.2.0" +Automat = ">=0.3.0" constantly = ">=15.1" hyperlink = ">=17.1.1" +idna = {version = ">=0.6,<2.3 || >2.3", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" +PyHamcrest = ">=1.9.0,<1.10.0 || >1.10.0" +pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} +service_identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} "zope.interface" = ">=4.4.2" [package.extras] -all_non_platform = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +all_non_platform = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] conch = ["pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] dev = ["pyflakes (>=1.0.0)", "twisted-dev-tools (>=0.0.2)", "python-subunit", "sphinx (>=1.3.1)", "towncrier (>=17.4.0)"] http2 = ["h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)"] -macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] -osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] soap = ["soappy"] -tls = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)"] -windows_platform = ["pywin32 (!=226)", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +tls = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)"] +windows_platform = ["pywin32 (!=226)", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] [[package]] -category = "main" -description = "Compatibility API between asyncio/Twisted/Trollius" name = "txaio" +version = "21.2.1" +description = "Compatibility API between asyncio/Twisted/Trollius" +category = "main" optional = false python-versions = ">=3.6" -version = "21.2.1" [package.extras] all = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.3" [[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false python-versions = "*" -version = "3.10.0.0" [[package]] -category = "dev" -description = "URI templates" name = "uritemplate" +version = "3.0.1" +description = "URI templates" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.1" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.5" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "main" -description = "The lightning-fast ASGI server." name = "uvicorn" +version = "0.13.4" +description = "The lightning-fast ASGI server." +category = "main" optional = false python-versions = "*" -version = "0.13.4" [package.dependencies] click = ">=7.0.0,<8.0.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" - -[package.dependencies.PyYAML] -optional = true -version = ">=5.1" - -[package.dependencies.colorama] -optional = true -version = ">=0.4" - -[package.dependencies.httptools] -optional = true -version = ">=0.1.0,<0.2.0" - -[package.dependencies.python-dotenv] -optional = true -version = ">=0.13" - -[package.dependencies.uvloop] -optional = true -version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1" - -[package.dependencies.watchgod] -optional = true -version = ">=0.6" - -[package.dependencies.websockets] -optional = true -version = ">=8.0.0,<9.0.0" +httptools = {version = ">=0.1.0,<0.2.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=8.0.0,<9.0.0", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1)", "colorama (>=0.4)"] +standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] [[package]] -category = "main" -description = "Fast implementation of asyncio event loop on top of libuv" -marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" name = "uvloop" +version = "0.15.2" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" optional = false python-versions = ">=3.7" -version = "0.15.2" [package.extras] dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] @@ -1536,40 +1356,36 @@ docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sp test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] [[package]] -category = "main" -description = "Simple, modern file watching and code reload in python." name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" optional = false python-versions = ">=3.5" -version = "0.7" [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" -version = "0.2.5" [[package]] -category = "main" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" name = "websockets" +version = "8.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" optional = false python-versions = ">=3.6.1" -version = "8.1" [[package]] -category = "main" -description = "Interfaces for Python" -marker = "sys_platform == \"linux\"" name = "zope.interface" +version = "5.4.0" +description = "Interfaces for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.4.0" - -[package.dependencies] -setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1577,27 +1393,17 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [[package]] -category = "main" -description = "" name = "zxcvbn" +version = "4.4.28" +description = "" +category = "main" optional = false python-versions = "*" -version = "4.4.28" [metadata] -<<<<<<< HEAD lock-version = "1.1" python-versions = "^3.9" -content-hash = "226b334134372a933b21b2fbef2fd5765accbe3586679b27c0a0c29589641b05" -||||||| 8b20521 -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "53dc2a3c8189802d7712c53e3812d651a8a083d8e7e1fe74ee1e79a53b7d0da1" -======= -content-hash = "f6245355f0ebded9442ad66865bc8be5a91287c71dd63b5b491d652c14065db4" -lock-version = "1.0" -python-versions = "^3.9" ->>>>>>> master +content-hash = "97149bd25ead908f42e3b0746abb0a69933099976c21265b33ef37e5b3594521" [metadata.files] aioredis = [ @@ -1651,98 +1457,18 @@ black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] boto3 = [ - {file = "boto3-1.17.75-py2.py3-none-any.whl", hash = "sha256:7059b3dbb6b474c6ef836aa3bb53eb7b3e474133f3e4cb2a3a3adcc55e2b2b9d"}, - {file = "boto3-1.17.75.tar.gz", hash = "sha256:bc8b6e98375f71dde0f51e08d415a72e3d8389042ea07d216ac82c98060e7149"}, + {file = "boto3-1.17.88-py2.py3-none-any.whl", hash = "sha256:13afcc5e2fcc5e4f9eab1ee46a769cf738a259dcd45f71ee79255f18973e4584"}, + {file = "boto3-1.17.88.tar.gz", hash = "sha256:a715ca6c4457d56ea3e3efde9bdc8be41c29b2f2a904fbd12befdb9cb5e289e4"}, ] botocore = [ - {file = "botocore-1.20.75-py2.py3-none-any.whl", hash = "sha256:80fc9a463bcc6368ed95d4b10717f5bb87288b930b3ccf96ce480d1c65e20e5c"}, - {file = "botocore-1.20.75.tar.gz", hash = "sha256:ab1bd5d5f86a4fdd194129a6102e5193209d3c4676dd76881769d6894bbaab7e"}, -] -cachalot = [ - {file = "Cachalot-1.5.0-py3-none-any.whl", hash = "sha256:426a42d966c624b3524d53aaabf1438373168f78b2f1123283fdb20470a37432"}, - {file = "Cachalot-1.5.0.tar.gz", hash = "sha256:2dac05118e746f3eb9aea7de20327610b6619e41cb145cb5bfd9098e7f901c43"}, + {file = "botocore-1.20.88-py2.py3-none-any.whl", hash = "sha256:be3cb73fab60a2349e2932bd0cbbe7e7736e3a2cd8c05b539d362ff3e406be76"}, + {file = "botocore-1.20.88.tar.gz", hash = "sha256:bc989edab52d4788aadd8d1aff925f5c6a7cbc68900bfdb8e379965aeac17317"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] cffi = [ -<<<<<<< HEAD - {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, - {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, - {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, - {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, - {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, - {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, - {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, - {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, - {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, - {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, - {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, - {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, - {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, - {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, - {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, - {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"}, - {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, - {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, - {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, -||||||| 8b20521 - {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, - {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, - {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, - {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, - {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, - {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, - {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, - {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, - {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, - {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, - {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, - {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, - {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, - {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, - {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, - {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, - {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, - {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, - {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, -======= {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, @@ -1780,7 +1506,6 @@ cffi = [ {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ->>>>>>> master ] channels = [ {file = "channels-2.4.0-py2.py3-none-any.whl", hash = "sha256:80a5ad1962ae039a3dcc0a5cb5212413e66e2f11ad9e9db8004834436daf3400"}, @@ -1790,6 +1515,10 @@ channels-redis = [ {file = "channels_redis-2.4.1-py2.py3-none-any.whl", hash = "sha256:de3ecced30b43df61bc944b84d45ebbaf008fdf1a4154fc8ee7c9c6e78f00a4c"}, {file = "channels_redis-2.4.1.tar.gz", hash = "sha256:ddfa0c067085fdce24fb80d9c0b848638cbdbf0e1167f14eb2e99d635ad216e6"}, ] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -1887,12 +1616,12 @@ decorator = [ {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, ] django = [ - {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, - {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, + {file = "Django-3.2.4-py3-none-any.whl", hash = "sha256:ea735cbbbb3b2fba6d4da4784a0043d84c67c92f1fdf15ad6db69900e792c10f"}, + {file = "Django-3.2.4.tar.gz", hash = "sha256:66c9d8db8cc6fe938a28b7887c1596e42d522e27618562517cc8929eb7e7f296"}, ] django-cachalot = [ - {file = "django-cachalot-2.3.5.tar.gz", hash = "sha256:02afabb6e83f5f06c87a7e6f01ebcdbc52a4156ec849da8e68b14498bc474d3e"}, - {file = "django_cachalot-2.3.5-py3-none-any.whl", hash = "sha256:ed0782f9702ead95337692f0fae8bbb9352a106490f272d9b76e86b1da81c7e3"}, + {file = "django-cachalot-2.4.1.tar.gz", hash = "sha256:9859ed59f215090c24ad6a5d654b693c47a8d802132695cf238a1d384df1e245"}, + {file = "django_cachalot-2.4.1-py3-none-any.whl", hash = "sha256:744e4ec03cc5a440303524f759f637b1ecc1cbf1232f36ebd1dae835da5853d1"}, ] django-cors-headers = [ {file = "django-cors-headers-3.2.1.tar.gz", hash = "sha256:a5960addecc04527ab26617e51b8ed42f0adab4594b24bb0f3c33e2bd3857c3f"}, @@ -2017,8 +1746,8 @@ hyperlink = [ {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"}, ] idna = [ - {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"}, - {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] incremental = [ {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, @@ -2029,8 +1758,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipython = [ - {file = "ipython-7.23.1-py3-none-any.whl", hash = "sha256:f78c6a3972dde1cc9e4041cbf4de583546314ba52d3c97208e5b6b2221a9cb7d"}, - {file = "ipython-7.23.1.tar.gz", hash = "sha256:714810a5c74f512b69d5f3b944c86e592cee0a5fb9c728e582f074610f6cf038"}, + {file = "ipython-7.24.1-py3-none-any.whl", hash = "sha256:d513e93327cf8657d6467c81f1f894adc125334ffe0e4ddd1abbb1c78d828703"}, + {file = "ipython-7.24.1.tar.gz", hash = "sha256:9bc24a99f5d19721fb8a2d1408908e9c0520a17fff2233ffe82620847f17f1b6"}, ] ipython-genutils = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, @@ -2052,99 +1781,7 @@ jmespath = [ {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, ] -jsonpickle = [ - {file = "jsonpickle-2.0.0-py2.py3-none-any.whl", hash = "sha256:c1010994c1fbda87a48f8a56698605b598cb0fc6bb7e7927559fc1100e69aeac"}, - {file = "jsonpickle-2.0.0.tar.gz", hash = "sha256:0be49cba80ea6f87a168aa8168d717d00c6ca07ba83df3cec32d3b30bfe6fb9a"}, -] markupsafe = [ -<<<<<<< HEAD - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -||||||| 8b20521 - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -======= {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, @@ -2183,7 +1820,6 @@ markupsafe = [ matplotlib-inline = [ {file = "matplotlib-inline-0.1.2.tar.gz", hash = "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"}, {file = "matplotlib_inline-0.1.2-py3-none-any.whl", hash = "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811"}, ->>>>>>> master ] msgpack = [ {file = "msgpack-0.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170"}, @@ -2276,8 +1912,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] prometheus-client = [ - {file = "prometheus_client-0.10.1-py2.py3-none-any.whl", hash = "sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa"}, - {file = "prometheus_client-0.10.1.tar.gz", hash = "sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"}, + {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, + {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.18-py3-none-any.whl", hash = "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04"}, @@ -2314,13 +1950,15 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, ] ptyprocess = [ -<<<<<<< HEAD - {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, - {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, @@ -2355,43 +1993,6 @@ pyasn1-modules = [ {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, -||||||| 8b20521 - {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, - {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, -] -pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, - {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, - {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, -] -pyasn1-modules = [ - {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, - {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, - {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, - {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, - {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, - {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, - {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, - {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, - {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, - {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, - {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, - {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, -======= - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ->>>>>>> master ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, @@ -2413,6 +2014,10 @@ pyhamcrest = [ {file = "PyHamcrest-2.0.2-py3-none-any.whl", hash = "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"}, {file = "PyHamcrest-2.0.2.tar.gz", hash = "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316"}, ] +pyopenssl = [ + {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, + {file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"}, +] pyotp = [ {file = "pyotp-2.3.0-py2.py3-none-any.whl", hash = "sha256:c88f37fd47541a580b744b42136f387cdad481b560ef410c0d85c957eb2a2bc0"}, {file = "pyotp-2.3.0.tar.gz", hash = "sha256:fc537e8acd985c5cbf51e11b7d53c42276fee017a73aec7c07380695671ca1a1"}, @@ -2426,12 +2031,12 @@ pytest = [ {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, - {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytest-django = [ - {file = "pytest-django-4.3.0.tar.gz", hash = "sha256:d1c6758a592fb0ef8abaa2fe12dd28858c1dcfc3d466102ffe52aa8934733dca"}, - {file = "pytest_django-4.3.0-py3-none-any.whl", hash = "sha256:f96c4556f4e7b15d987dd1dcc1d1526df81d40c1548d31ce840d597ed2be8c46"}, + {file = "pytest-django-4.4.0.tar.gz", hash = "sha256:b5171e3798bf7e3fc5ea7072fe87324db67a4dd9f1192b037fed4cc3c1b7f455"}, + {file = "pytest_django-4.4.0-py3-none-any.whl", hash = "sha256:65783e78382456528bd9d79a35843adde9e6a47347b20464eb2c885cb0f1f606"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, @@ -2527,8 +2132,8 @@ regex = [ {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, ] requests = [ - {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"}, - {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] s3transfer = [ {file = "s3transfer-0.4.2-py2.py3-none-any.whl", hash = "sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc"}, @@ -2539,28 +2144,16 @@ sendgrid = [ {file = "sendgrid-6.7.0.tar.gz", hash = "sha256:74b0dcf9a79188948f61f456bd1bf67ffa676a5d388aba1c76bff516566d7084"}, ] sentry-sdk = [ -<<<<<<< HEAD - {file = "sentry-sdk-0.16.5.tar.gz", hash = "sha256:e12eb1c2c01cd9e9cfe70608dbda4ef451f37ef0b7cbb92e5d43f87c341d6334"}, - {file = "sentry_sdk-0.16.5-py2.py3-none-any.whl", hash = "sha256:d359609e23ec9360b61e5ffdfa417e2f6bca281bfb869608c98c169c7e64acd5"}, + {file = "sentry-sdk-1.1.0.tar.gz", hash = "sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739"}, + {file = "sentry_sdk-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"}, ] serpy = [ {file = "serpy-0.3.1-py2.py3-none-any.whl", hash = "sha256:750ded3df0671918b81d6efcab2b85cac12f9fcc2bce496c24a0ffa65d84b5da"}, {file = "serpy-0.3.1.tar.gz", hash = "sha256:3772b2a9923fbf674000ff51abebf6ea8f0fca0a2cfcbfa0d63ff118193d1ec5"}, ] service-identity = [ - {file = "service_identity-18.1.0-py2.py3-none-any.whl", hash = "sha256:001c0707759cb3de7e49c078a7c0c9cd12594161d3bf06b9c254fdcb1a60dc36"}, - {file = "service_identity-18.1.0.tar.gz", hash = "sha256:0858a54aabc5b459d1aafa8a518ed2081a285087f349fe3e55197989232e2e2d"}, -||||||| 8b20521 - {file = "sentry-sdk-0.16.5.tar.gz", hash = "sha256:e12eb1c2c01cd9e9cfe70608dbda4ef451f37ef0b7cbb92e5d43f87c341d6334"}, - {file = "sentry_sdk-0.16.5-py2.py3-none-any.whl", hash = "sha256:d359609e23ec9360b61e5ffdfa417e2f6bca281bfb869608c98c169c7e64acd5"}, -] -service-identity = [ - {file = "service_identity-18.1.0-py2.py3-none-any.whl", hash = "sha256:001c0707759cb3de7e49c078a7c0c9cd12594161d3bf06b9c254fdcb1a60dc36"}, - {file = "service_identity-18.1.0.tar.gz", hash = "sha256:0858a54aabc5b459d1aafa8a518ed2081a285087f349fe3e55197989232e2e2d"}, -======= - {file = "sentry-sdk-1.1.0.tar.gz", hash = "sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739"}, - {file = "sentry_sdk-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"}, ->>>>>>> master + {file = "service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"}, + {file = "service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -2573,14 +2166,6 @@ sqlparse = [ starkbank-ecdsa = [ {file = "starkbank-ecdsa-1.1.0.tar.gz", hash = "sha256:423f81bb55c896a3c85ee98ac7da98826721eaee918f5c0c1dfff99e1972da0c"}, ] -tinydb = [ - {file = "tinydb-4.4.0-py3-none-any.whl", hash = "sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71"}, - {file = "tinydb-4.4.0.tar.gz", hash = "sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a"}, -] -tinydb-smartcache = [ - {file = "tinydb-smartcache-2.0.0.tar.gz", hash = "sha256:a1f6034d8fe28a72810fdbef46a0547949cbf7e9b43a69343dc0e1adfb47f04a"}, - {file = "tinydb_smartcache-2.0.0-py3-none-any.whl", hash = "sha256:c162fe6c558bb73ff1e6b33b168807ea87063e47e2b85cb9e84c12d4354aa7c8"}, -] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -2660,8 +2245,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, + {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, ] uvicorn = [ {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"}, From ba12b7b10d773ca7853b9b1e8dd4bc4342c37f8a Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:04:13 +0100 Subject: [PATCH 052/185] Update decay.py --- src/plugins/points/decay.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/plugins/points/decay.py b/src/plugins/points/decay.py index bb067f6f..accd646f 100644 --- a/src/plugins/points/decay.py +++ b/src/plugins/points/decay.py @@ -22,9 +22,5 @@ def recalculate(self, teams, users, solves, *args, **kwargs): delta = self.get_points(None, None, solves.count() - 1) - points scores = Score.objects.filter(solve__in=solves) scores.update(points=points) - Team.objects.filter(solves__challenge=challenge).update( - points=F("points") - delta - ) - get_user_model().objects.filter(solves__challenge=challenge).update( - points=F("points") - delta - ) + Team.objects.filter(solves__challenge=challenge).update(points=F("points") - delta) + get_user_model().objects.filter(solves__challenge=challenge).update(points=F("points") - delta) From 77b846abb6f78c1fb2df5ef3a496a79c0a87a022 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:15:42 +0100 Subject: [PATCH 053/185] Merge diverging migrations --- ...6_auto_20210327_2315_0016_auto_20210408_1804.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/challenge/migrations/0017_merge_0016_auto_20210327_2315_0016_auto_20210408_1804.py diff --git a/src/challenge/migrations/0017_merge_0016_auto_20210327_2315_0016_auto_20210408_1804.py b/src/challenge/migrations/0017_merge_0016_auto_20210327_2315_0016_auto_20210408_1804.py new file mode 100644 index 00000000..6b75c2f8 --- /dev/null +++ b/src/challenge/migrations/0017_merge_0016_auto_20210327_2315_0016_auto_20210408_1804.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.4 on 2021-06-06 20:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('challenge', '0016_auto_20210327_2315'), + ('challenge', '0016_auto_20210408_1804'), + ] + + operations = [ + ] From 4dc17b2916dc999dadcab5279c91f343a9b9ea89 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:15:58 +0100 Subject: [PATCH 054/185] Add newline at end of apps.py --- src/stats/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/apps.py b/src/stats/apps.py index 6a4664eb..2ad9584e 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -23,4 +23,4 @@ def ready(self): signals.team_count.set(Team.objects.count()) signals.solve_count.set(Solve.objects.count()) signals.member_count.set(Member.objects.count()) - signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) \ No newline at end of file + signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) From 73566ac36bba5d37d5a92029233ed3b040b40ee7 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:16:27 +0100 Subject: [PATCH 055/185] Remove re-definition of prometheus Gauges --- src/stats/views.py | 70 +++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/stats/views.py b/src/stats/views.py index 65f40d42..62169bec 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -5,7 +5,6 @@ from django.core.cache import cache from django.db.models import Sum from django_prometheus.exports import ExportToDjangoView -from prometheus_client import Gauge from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView @@ -15,25 +14,23 @@ from challenge.models import Score from challenge.sql import get_incorrect_solve_counts, get_solve_counts from member.models import UserIP +from stats.signals import member_count, team_count, solve_count, correct_solve_count from team.models import Team -member_count = Gauge("member_count", "The number of members currently registered") -team_count = Gauge("team_count", "The number of teams currently registered") -solve_count = Gauge("solve_count", "The count of both correct and incorrect solves") -correct_solve_count = Gauge("correct_solve_count", "The count of correct solves") - -@api_view(['GET']) +@api_view(["GET"]) def countdown(request): - return FormattedResponse({ - "countdown_timestamp": config.config.get('start_time'), - "registration_open": config.config.get('register_start_time'), - "competition_end": config.config.get('end_time'), - "server_timestamp": datetime.now(timezone.utc).isoformat(), - }) + return FormattedResponse( + { + "countdown_timestamp": config.config.get("start_time"), + "registration_open": config.config.get("register_start_time"), + "competition_end": config.config.get("end_time"), + "server_timestamp": datetime.now(timezone.utc).isoformat(), + } + ) -@api_view(['GET']) +@api_view(["GET"]) def stats(request): users = get_user_model().objects.count() teams = Team.objects.count() @@ -45,16 +42,18 @@ def stats(request): solve_count = sum(get_solve_counts().values()) total_solve_count = solve_count + sum(get_incorrect_solve_counts().values()) - return FormattedResponse({ - "user_count": users, - "team_count": teams, - "solve_count": total_solve_count, - "correct_solve_count": solve_count, - "avg_members": average, - }) + return FormattedResponse( + { + "user_count": users, + "team_count": teams, + "solve_count": total_solve_count, + "correct_solve_count": solve_count, + "avg_members": average, + } + ) -@api_view(['GET']) +@api_view(["GET"]) @permission_classes([IsAdminUser]) def full(request): challenge_data = {} @@ -72,28 +71,23 @@ def full(request): point_distribution[team.points] = 0 point_distribution[team.points] += 1 - return FormattedResponse({ - "users": { - "all": get_user_model().objects.count(), - "confirmed": get_user_model().objects.filter(email_verified=True).count() - }, - "teams": Team.objects.count(), - "ips": UserIP.objects.count(), - "total_points": Score.objects.all().aggregate(Sum('points'))["points__sum"], - "challenges": challenge_data, - "team_point_distribution": point_distribution - }) - - -@api_view(['GET']) -def version(request): return FormattedResponse( { - "commit_hash": os.popen("git rev-parse HEAD").read().strip() + "users": {"all": get_user_model().objects.count(), "confirmed": get_user_model().objects.filter(email_verified=True).count()}, + "teams": Team.objects.count(), + "ips": UserIP.objects.count(), + "total_points": Score.objects.all().aggregate(Sum("points"))["points__sum"], + "challenges": challenge_data, + "team_point_distribution": point_distribution, } ) +@api_view(["GET"]) +def version(request): + return FormattedResponse({"commit_hash": os.popen("git rev-parse HEAD").read().strip()}) + + class PrometheusMetricsView(APIView): permission_classes = [IsAdminUser] From cad908530d320b09a7237b679e57b60c5b177844 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:16:55 +0100 Subject: [PATCH 056/185] Pull Gauge counts from cache directly after set --- src/stats/signals.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/stats/signals.py b/src/stats/signals.py index abd9449c..bf70edf0 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -10,27 +10,23 @@ member_count = Gauge("member_count", "The number of members currently registered") -member_count.set(Member.objects.count()) - team_count = Gauge("team_count", "The number of teams currently registered") -team_count.set(Team.objects.count()) - solve_count = Gauge("solve_count", "The count of both correct and incorrect solves") -solve_count.set(Solve.objects.count()) - correct_solve_count = Gauge("correct_solve_count", "The count of correct solves") -correct_solve_count.set(Solve.objects.filter(correct=True).count()) +connected_websocket_users = Gauge("connected_websocket_users", "The number of users connected to the websocket", multiprocess_mode="livesum") -# When the worker starts up, set these in the cache to stay in sync if not cache.get("migrations_needed"): cache.set("member_count", Member.objects.count(), timeout=None) + member_count.set(cache.get("member_count")) + cache.set("team_count", Team.objects.count(), timeout=None) + team_count.set(cache.get("team_count")) + cache.set("solve_count", Solve.objects.count(), timeout=None) - cache.set("correct_solve_count", Solve.objects.filter(correct=True).count(), timeout=None) + solve_count.set(cache.get("solve_count")) -connected_websocket_users = Gauge( - "connected_websocket_users", "The number of users connected to the websocket", multiprocess_mode="livesum" -) + cache.set("correct_solve_count", Solve.objects.filter(correct=True).count(), timeout=None) + correct_solve_count.set(cache.get("correct_solve_count")) @receiver(register) From 024c3cb48f5a3bc923826963968aa91c22851e8c Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:17:20 +0100 Subject: [PATCH 057/185] Update decay plugin to no longer import from model namespace --- src/plugins/points/decay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/points/decay.py b/src/plugins/points/decay.py index accd646f..48eeee78 100644 --- a/src/plugins/points/decay.py +++ b/src/plugins/points/decay.py @@ -1,9 +1,9 @@ from django.contrib.auth import get_user_model from django.db.models import F +import team from challenge.models import Score from plugins.points.base import PointsPlugin -from team.models import Team class DecayPointsPlugin(PointsPlugin): @@ -22,5 +22,5 @@ def recalculate(self, teams, users, solves, *args, **kwargs): delta = self.get_points(None, None, solves.count() - 1) - points scores = Score.objects.filter(solve__in=solves) scores.update(points=points) - Team.objects.filter(solves__challenge=challenge).update(points=F("points") - delta) + team.models.Team.objects.filter(solves__challenge=challenge).update(points=F("points") - delta) get_user_model().objects.filter(solves__challenge=challenge).update(points=F("points") - delta) From ba9de78037dec497c054a7bf0f0a7b37b0f7f8ae Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:22:51 +0100 Subject: [PATCH 058/185] Remove faulty migration operation --- src/challenge/migrations/0016_auto_20210327_2315.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/challenge/migrations/0016_auto_20210327_2315.py b/src/challenge/migrations/0016_auto_20210327_2315.py index 33e71547..99b4849e 100644 --- a/src/challenge/migrations/0016_auto_20210327_2315.py +++ b/src/challenge/migrations/0016_auto_20210327_2315.py @@ -6,17 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('challenge', '0015_auto_20210131_1809'), + ("challenge", "0015_auto_20210131_1809"), ] operations = [ - migrations.RemoveField( - model_name='challenge', - name='auto_unlock', - ), migrations.AlterField( - model_name='challenge', - name='unlock_requirements', + model_name="challenge", + name="unlock_requirements", field=models.CharField(blank=True, max_length=255, null=True), ), ] From ba6cbaec94449e3723e3fa3249af18985f86b73c Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Sun, 6 Jun 2021 21:26:59 +0100 Subject: [PATCH 059/185] Remove old challenge/tests.py --- src/challenge/tests.py | 356 ----------------------------------------- 1 file changed, 356 deletions(-) delete mode 100644 src/challenge/tests.py diff --git a/src/challenge/tests.py b/src/challenge/tests.py deleted file mode 100644 index 692afca0..00000000 --- a/src/challenge/tests.py +++ /dev/null @@ -1,356 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.models import AnonymousUser -from django.urls import reverse -from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED, \ - HTTP_400_BAD_REQUEST -from rest_framework.test import APITestCase - -from challenge.models import Category, Challenge, Solve -from config import config -from hint.models import Hint, HintUse -from team.models import Team - - -class ChallengeSetupMixin: - - def setUp(self): - category = Category(name='test', display_order=0, contained_type='test', description='') - category.save() - challenge1 = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge2 = Challenge(name='test2', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge1.save() - challenge2.save() - challenge1.unlock_requirements = "2" - challenge1.save() - hint1 = Hint(name='hint1', challenge=challenge1, text='a', penalty=100) - hint2 = Hint(name='hint2', challenge=challenge1, text='a', penalty=100) - hint3 = Hint(name='hint3', challenge=challenge2, text='a', penalty=100) - hint1.save() - hint2.save() - hint3.save() - user = get_user_model()(username='challenge-test', email='challenge-test@example.org') - user.save() - team = Team(name='team', password='password', owner=user) - team.save() - user.team = team - user.save() - user2 = get_user_model()(username='challenge-test-2', email='challenge-test-2@example.org') - user2.team = team - user2.save() - user3 = get_user_model()(username='challenge-test-3', email='challenge-test-3@example.org') - user3.save() - team2 = Team(name='team2', password='password', owner=user3) - team2.save() - user3.team = team2 - user3.save() - self.user = user - self.user2 = user2 - self.user3 = user3 - self.team = team - self.team2 = team2 - self.category = category - self.challenge1 = challenge1 - self.challenge2 = challenge2 - self.hint1 = hint1 - self.hint2 = hint2 - self.hint3 = hint3 - - -class ChallengeTestCase(ChallengeSetupMixin, APITestCase): - - def solve_challenge(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - return self.client.post(reverse('submit-flag'), data) - - def test_challenge_solve(self): - response = self.solve_challenge() - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(response.data['d']['correct'], True) - - def test_challenge_solve_incorrect_flag(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{b}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertEquals(response.data['d']['correct'], False) - - def test_challenge_double_solve(self): - self.solve_challenge() - self.client.force_authenticate(user=self.user2) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - - def test_challenge_unlocks(self): - self.solve_challenge() - self.challenge1.unlock_requirements = str(self.challenge2.id) - self.assertTrue(self.challenge1.is_unlocked(get_user_model().objects.get(id=self.user.id))) - - def test_challenge_unlocks_no_team(self): - user4 = get_user_model()(username='challenge-test-4', email='challenge-test-4@example.org') - user4.save() - self.assertFalse(self.challenge1.is_unlocked(user4)) - - def test_hint_scoring(self): - HintUse(hint=self.hint3, team=self.team, user=self.user, challenge=self.challenge2).save() - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['points'], 900) - - def test_solve_first_blood(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['first_blood'], True) - - def test_solve_solved_by_name(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['solved_by_name'], 'challenge-test') - - def test_solve_team_name(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['team_name'], 'team') - - def test_normal_scoring(self): - self.solve_challenge() - response = self.client.get(reverse('team-self')) - self.assertEquals(response.data['solves'][0]['points'], 1000) - - def test_is_solved(self): - self.solve_challenge() - self.assertTrue(self.challenge2.is_solved(user=self.user)) - - def test_is_not_solved(self): - self.assertFalse(self.challenge1.is_solved(user=self.user)) - - def test_submission_disabled(self): - config.set('enable_flag_submission', False) - response = self.solve_challenge() - config.set('enable_flag_submission', True) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - - def test_submission_malformed(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_400_BAD_REQUEST) - - def test_challenge_score_same_team(self): - self.client.force_authenticate(user=self.user) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - self.client.post(reverse('submit-flag'), data) - self.client.force_authenticate(user=self.user2) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - - def test_challenge_score_not_first_blood(self): - self.solve_challenge() - self.client.force_authenticate(user=self.user3) - data = { - 'flag': 'ractf{a}', - 'challenge': self.challenge2.id, - } - response = self.client.post(reverse('submit-flag'), data) - self.assertEquals(response.status_code, HTTP_200_OK) - self.assertFalse(Solve.objects.get(team=self.team2, challenge=self.challenge2).first_blood) - - def test_challenge_solved_unauthed(self): - self.assertFalse(self.challenge2.is_solved(AnonymousUser())) - - def test_challenge_unlocked_unauthed(self): - self.assertFalse(self.challenge2.is_unlocked(AnonymousUser())) - - def test_challenge_solved_no_team(self): - user4 = get_user_model()(username='challenge-test-4', email='challenge-test-4@example.org') - user4.save() - self.assertFalse(self.challenge2.is_solved(user4)) - - -class CategoryViewsetTestCase(ChallengeSetupMixin, APITestCase): - - def test_category_list_unauthenticated_permission(self): - response = self.client.get(reverse('categories-list')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) - - def test_category_list_authenticated_permission(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertEquals(response.status_code, HTTP_200_OK) - - def test_category_list_unauthenticated_content(self): - response = self.client.get(reverse('categories-list')) - self.assertFalse(response.data['s']) - self.assertEquals(response.data['m'], 'not_authenticated') - self.assertEquals(response.data['d'], '') - - def test_category_list_authenticated_content(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertEquals(len(response.data['d']), 1) - self.assertEquals(len(response.data['d'][0]['challenges']), 2) - - def test_category_list_challenge_redacting(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertFalse('description' in response.data['d'][0]['challenges'][0]) - - def test_category_list_challenge_redacting_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertTrue('description' in response.data['d'][0]['challenges'][0]) - - def test_category_list_challenge_unlocked_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('categories-list')) - self.assertFalse(response.data['d'][0]['challenges'][0]['unlocked']) - - def test_category_create(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('categories-list'), data={ - 'name': 'test-category-2', - 'contained_type': 'test', - 'description': 'test', - }) - self.assertTrue(response.status_code, HTTP_200_OK) - - def test_category_create_unauthorized(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('categories-list'), data={ - 'name': 'test-category-2', - 'contained_type': 'test', - 'description': 'test', - }) - self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) - - -class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): - - def test_challenge_list_unauthenticated_permission(self): - response = self.client.get(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_401_UNAUTHORIZED) - - def test_challenge_list_authenticated_permission(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_200_OK) - - def test_challenge_list_unauthenticated_content(self): - response = self.client.get(reverse('challenges-list')) - self.assertFalse(response.data['s']) - self.assertEquals(response.data['m'], 'not_authenticated') - self.assertEquals(response.data['d'], '') - - def test_challenge_list_authenticated_content(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertEquals(len(response.data), 2) - - def test_challenge_list_challenge_redacting(self): - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertFalse('description' in response.data[0]) - - def test_challenge_list_challenge_redacting_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertTrue('description' in response.data[0]) - - def test_challenge_list_challenge_unlocked_admin(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-list')) - self.assertFalse(response.data[0]['unlocked']) - - def test_single_challenge_redacting(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertFalse('description' in response.data) - - def test_single_challenge_admin_redacting(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertTrue('description' in response.data) - - def test_admin_unlocking(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.get(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertFalse(response.data['unlocked']) - - def test_user_post_detail(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-detail', kwargs={'pk': self.challenge1.id})) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - - def test_user_post_list(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list')) - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - - def test_create_challenge(self): - self.user.is_staff = True - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list'), data={ - 'name': 'test4', 'category': self.category.id, 'description': 'abc', - 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'score': 1000, 'unlock_requirements': "", 'flag_metadata': {}, - 'tags': [], - }, format='json') - self.assertEquals(response.status_code, HTTP_201_CREATED) - - def test_create_challenge_unauthorized(self): - self.user.is_staff = False - self.user.save() - self.client.force_authenticate(self.user) - response = self.client.post(reverse('challenges-list'), data={ - 'name': 'test4', 'category': self.category.id, 'description': 'abc', - 'challenge_type': 'test', 'challenge_metadata': {}, 'flag_type': 'plaintext', - 'author': 'dave', 'score': 1000, 'unlock_requirements': "a", 'flag_metadata': {} - }, format='json') - self.assertEquals(response.status_code, HTTP_403_FORBIDDEN) - From f97789b54c7d425d40c91cce1136b34c60e1c93b Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Jun 2021 17:02:46 +0100 Subject: [PATCH 060/185] run black --- src/admin/apps.py | 2 +- src/admin/models.py | 1 - src/admin/tests.py | 18 +- src/admin/urls.py | 2 +- src/andromeda/client.py | 20 +- src/andromeda/models.py | 1 - src/andromeda/tests.py | 1 - src/andromeda/urls.py | 16 +- src/andromeda/views.py | 32 +- src/announcements/tests.py | 1 - src/authentication/apps.py | 4 +- src/authentication/basic_auth.py | 31 +- src/authentication/models.py | 8 +- src/authentication/permissions.py | 3 +- src/authentication/providers.py | 12 +- src/authentication/serializers.py | 62 +- src/authentication/tests.py | 712 +++++++++--------- src/authentication/urls.py | 38 +- src/authentication/views.py | 108 +-- src/backend/mail.py | 65 +- src/backend/pagination.py | 9 +- src/backend/permissions.py | 8 +- src/backend/renderers.py | 30 +- src/backend/response.py | 6 +- src/backend/settings/__init__.py | 70 +- src/backend/settings/local.py | 2 +- src/backend/settings/staging.py | 19 +- src/backend/settings/test.py | 12 +- src/backend/signals.py | 46 +- src/backend/storages.py | 4 +- src/backend/tests.py | 1 - src/backend/validators.py | 14 +- src/backend/views.py | 3 +- src/backend/viewsets.py | 4 +- src/challenge/models.py | 38 +- src/challenge/permissions.py | 6 +- src/challenge/serializers.py | 101 +-- src/challenge/signals.py | 4 +- src/challenge/sql.py | 44 +- src/challenge/urls.py | 20 +- src/challenge/views.py | 176 ++--- src/config/config.py | 6 +- src/config/tests.py | 24 +- src/config/urls.py | 5 +- src/experiments/apps.py | 2 +- src/experiments/models.py | 1 - src/experiments/urls.py | 2 +- src/gunicorn_config.py | 1 + src/hint/models.py | 4 +- src/hint/permissions.py | 9 +- src/hint/serializers.py | 10 +- src/hint/urls.py | 6 +- src/hint/views.py | 14 +- src/leaderboard/serializers.py | 4 +- src/leaderboard/tests.py | 164 ++-- src/leaderboard/urls.py | 12 +- src/leaderboard/views.py | 51 +- src/manage.py | 6 +- src/member/models.py | 7 +- src/member/serializers.py | 113 ++- src/member/tests.py | 36 +- src/member/urls.py | 9 +- src/member/views.py | 59 +- src/pages/apps.py | 2 +- src/pages/serializers.py | 2 +- src/pages/tests.py | 1 - src/pages/urls.py | 6 +- src/pages/views.py | 2 +- src/plugins/apps.py | 4 +- src/plugins/flag/hashed.py | 6 +- src/plugins/flag/lenient.py | 32 +- src/plugins/flag/long_text.py | 6 +- src/plugins/flag/map.py | 5 +- src/plugins/flag/plaintext.py | 2 +- src/plugins/points/base.py | 27 +- src/polaris/admin.py | 1 - src/polaris/apps.py | 2 +- src/polaris/client.py | 59 +- src/polaris/models.py | 1 - src/polaris/tests.py | 1 - src/polaris/urls.py | 14 +- src/polaris/views.py | 15 +- src/ractf/apps.py | 7 +- src/ractf/management/commands/copy_points.py | 2 +- src/ractf/management/commands/flush_db.py | 21 +- src/ractf/management/commands/getschema.py | 36 +- .../management/commands/insert_dummy_data.py | 37 +- .../commands/insert_mega_dummy_data.py | 44 +- src/ractf/management/commands/transfer.py | 8 +- src/ractf/management/commands/unteam.py | 4 +- src/ractf/models.py | 1 - src/ractf/tests.py | 1 - src/scorerecalculator/models.py | 1 - src/scorerecalculator/tests.py | 76 +- src/scorerecalculator/urls.py | 6 +- src/scorerecalculator/views.py | 4 +- src/sockets/routing.py | 8 +- src/sockets/signals.py | 109 +-- src/stats/tests.py | 10 +- src/stats/urls.py | 11 +- src/team/models.py | 4 +- src/team/permissions.py | 5 +- src/team/serializers.py | 4 +- src/team/tests.py | 86 +-- src/team/urls.py | 12 +- src/team/views.py | 21 +- 106 files changed, 1402 insertions(+), 1603 deletions(-) diff --git a/src/admin/apps.py b/src/admin/apps.py index 5bbf1224..86bb5bc7 100644 --- a/src/admin/apps.py +++ b/src/admin/apps.py @@ -2,4 +2,4 @@ class AdminConfig(AppConfig): - name = 'admin' + name = "admin" diff --git a/src/admin/models.py b/src/admin/models.py index 35e0d648..6b202199 100644 --- a/src/admin/models.py +++ b/src/admin/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/admin/tests.py b/src/admin/tests.py index 490723dc..6bddbf4e 100644 --- a/src/admin/tests.py +++ b/src/admin/tests.py @@ -12,22 +12,20 @@ def setUp(self): self.user = Member.objects.create(username="test", is_superuser=True, is_staff=True) def test_missing_points(self): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() x = Challenge.objects.create( - name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=0 + name="test1", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=0 ) self.client.force_authenticate(user=self.user) - response = self.client.get(reverse('self-check')) + response = self.client.get(reverse("self-check")) self.assertEquals(response.data["d"][0]["issue"], "missing_points") x.score = 5 x.save() - response = self.client.get(reverse('self-check')) + response = self.client.get(reverse("self-check")) self.assertEquals(len(response.data["d"]), 0) @@ -35,7 +33,7 @@ class BadFlagConfigTestCase(APITestCase): def setUp(self): self.user = Member.objects.create(username="test", is_superuser=True, is_staff=True) - self.category = Category(name='test', display_order=0, contained_type='test', description='') + self.category = Category(name="test", display_order=0, contained_type="test", description="") self.category.save() self.i = 0 @@ -65,13 +63,11 @@ def setUp(self): def create_challenge(self, typ, metadata): self.i += 1 Challenge.objects.create( - name=f'{self.i}', category=self.category, description='a', challenge_type="basic", - challenge_metadata={}, flag_type=typ, flag_metadata=metadata, - author='dave', score=1 + name=f"{self.i}", category=self.category, description="a", challenge_type="basic", challenge_metadata={}, flag_type=typ, flag_metadata=metadata, author="dave", score=1 ) def test_length(self): self.client.force_authenticate(user=self.user) - response = self.client.get(reverse('self-check')) + response = self.client.get(reverse("self-check")) self.assertEquals(len(response.data["d"]), 14) diff --git a/src/admin/urls.py b/src/admin/urls.py index bc1fd11c..4edbcc32 100644 --- a/src/admin/urls.py +++ b/src/admin/urls.py @@ -6,5 +6,5 @@ router = DefaultRouter() urlpatterns = [ - path('self_check/', views.SelfCheckView.as_view(), name='self-check'), + path("self_check/", views.SelfCheckView.as_view(), name="self-check"), ] diff --git a/src/andromeda/client.py b/src/andromeda/client.py index 96754ee6..103d578e 100644 --- a/src/andromeda/client.py +++ b/src/andromeda/client.py @@ -8,33 +8,25 @@ def post(path, **kwargs): - response = requests.post(f"{settings.ANDROMEDA_URL}/{path}", - headers={"Authorization": settings.ANDROMEDA_API_KEY}, **kwargs) + response = requests.post(f"{settings.ANDROMEDA_URL}/{path}", headers={"Authorization": settings.ANDROMEDA_API_KEY}, **kwargs) if response.status_code == HTTP_200_OK: return response.json() - raise FormattedException(m='challenge_server_error') + raise FormattedException(m="challenge_server_error") def get(path, **kwargs): - response = requests.get(f"{settings.ANDROMEDA_URL}/{path}", - headers={"Authorization": settings.ANDROMEDA_API_KEY}, **kwargs) + response = requests.get(f"{settings.ANDROMEDA_URL}/{path}", headers={"Authorization": settings.ANDROMEDA_API_KEY}, **kwargs) if response.status_code == HTTP_200_OK: return response.json() - raise FormattedException(m='challenge_server_error') + raise FormattedException(m="challenge_server_error") def get_instance(user_id, job_id): - return post("", json={ - "user": str(user_id), - "job": job_id - }) + return post("", json={"user": str(user_id), "job": job_id}) def request_reset(user_id, job_id): - return post("reset", json={ - "user": str(user_id), - "job": job_id - }) + return post("reset", json={"user": str(user_id), "job": job_id}) def list_jobs(): diff --git a/src/andromeda/models.py b/src/andromeda/models.py index 35e0d648..6b202199 100644 --- a/src/andromeda/models.py +++ b/src/andromeda/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/andromeda/tests.py b/src/andromeda/tests.py index 49290204..a39b155a 100644 --- a/src/andromeda/tests.py +++ b/src/andromeda/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/src/andromeda/urls.py b/src/andromeda/urls.py index ff0311d1..ca4feca5 100644 --- a/src/andromeda/urls.py +++ b/src/andromeda/urls.py @@ -3,12 +3,12 @@ from andromeda import views urlpatterns = [ - path('instance//', views.GetInstanceView.as_view(), name='get-instance'), - path('reset//', views.ResetInstanceView.as_view(), name='request-new-instance'), - path('jobs/', views.ListJobsView.as_view(), name='list-jobs'), - path('restart/', views.RestartJobView.as_view(), name='restart-job'), - path('instances/', views.ListInstancesView.as_view(), name='list-instances'), - path('sysinfo/', views.SysinfoView.as_view(), name='view-sysinfo'), - path('submit_job/', views.JobSubmitView.as_view(), name='submit-job'), - path('submit_job_raw/', views.JobSubmitRawView.as_view(), name='submit-job'), + path("instance//", views.GetInstanceView.as_view(), name="get-instance"), + path("reset//", views.ResetInstanceView.as_view(), name="request-new-instance"), + path("jobs/", views.ListJobsView.as_view(), name="list-jobs"), + path("restart/", views.RestartJobView.as_view(), name="restart-job"), + path("instances/", views.ListInstancesView.as_view(), name="list-instances"), + path("sysinfo/", views.SysinfoView.as_view(), name="view-sysinfo"), + path("submit_job/", views.JobSubmitView.as_view(), name="submit-job"), + path("submit_job_raw/", views.JobSubmitRawView.as_view(), name="submit-job"), ] diff --git a/src/andromeda/views.py b/src/andromeda/views.py index 9339873c..48b748ac 100644 --- a/src/andromeda/views.py +++ b/src/andromeda/views.py @@ -13,9 +13,7 @@ class GetInstanceView(APIView): throttle_scope = "challenge_instance_get" def get(self, request, job_id): - return FormattedResponse( - client.get_instance(request.user.id, job_id) - ) + return FormattedResponse(client.get_instance(request.user.id, job_id)) class ResetInstanceView(APIView): @@ -23,9 +21,7 @@ class ResetInstanceView(APIView): throttle_scope = "challenge_instance_reset" def get(self, request, job_id): - return FormattedResponse( - client.request_reset(request.user.id, job_id) - ) + return FormattedResponse(client.request_reset(request.user.id, job_id)) class ListJobsView(APIView): @@ -33,9 +29,7 @@ class ListJobsView(APIView): throttle_scope = "andromeda_view_jobs" def get(self, request): - return FormattedResponse( - client.list_jobs() - ) + return FormattedResponse(client.list_jobs()) class RestartJobView(APIView): @@ -43,9 +37,7 @@ class RestartJobView(APIView): throttle_scope = "andromeda_manage_jobs" def post(self, request): - return FormattedResponse( - client.restart_job(request.data["job_id"]) - ) + return FormattedResponse(client.restart_job(request.data["job_id"])) class ListInstancesView(APIView): @@ -53,9 +45,7 @@ class ListInstancesView(APIView): throttle_scope = "andromeda_view_jobs" def get(self, request): - return FormattedResponse( - client.list_instances() - ) + return FormattedResponse(client.list_instances()) class SysinfoView(APIView): @@ -63,9 +53,7 @@ class SysinfoView(APIView): throttle_scope = "andromeda_view_sysinfo" def get(self, request): - return FormattedResponse( - client.sysinfo() - ) + return FormattedResponse(client.sysinfo()) class JobSubmitView(APIView): @@ -74,9 +62,9 @@ class JobSubmitView(APIView): def post(self, request): serializer = JobSubmitSerializer(request.data) - challenge = get_object_or_404(Challenge.objects, id=serializer.data['challenge_id']) - response = client.submit_job(serializer.data['job_spec']) - challenge.challenge_metadata['cserv_name'] = response['id'] + challenge = get_object_or_404(Challenge.objects, id=serializer.data["challenge_id"]) + response = client.submit_job(serializer.data["job_spec"]) + challenge.challenge_metadata["cserv_name"] = response["id"] challenge.save() return FormattedResponse() @@ -87,5 +75,5 @@ class JobSubmitRawView(APIView): def post(self, request): serializer = JobSubmitSerializer(request.data) - response = client.submit_job(serializer.data['job_spec']) + response = client.submit_job(serializer.data["job_spec"]) return FormattedResponse(response) diff --git a/src/announcements/tests.py b/src/announcements/tests.py index 49290204..a39b155a 100644 --- a/src/announcements/tests.py +++ b/src/announcements/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/src/authentication/apps.py b/src/authentication/apps.py index c2299e7f..58f6fcf8 100644 --- a/src/authentication/apps.py +++ b/src/authentication/apps.py @@ -3,6 +3,4 @@ class AuthConfig(PluginConfig): name = "authentication" - provides = ['authentication.basic_auth.BasicAuthLoginProvider', - 'authentication.basic_auth.BasicAuthRegistrationProvider', - 'authentication.basic_auth.BasicAuthTokenProvider'] + provides = ["authentication.basic_auth.BasicAuthLoginProvider", "authentication.basic_auth.BasicAuthRegistrationProvider", "authentication.basic_auth.BasicAuthTokenProvider"] diff --git a/src/authentication/basic_auth.py b/src/authentication/basic_auth.py index 4b021a18..04126c65 100644 --- a/src/authentication/basic_auth.py +++ b/src/authentication/basic_auth.py @@ -8,7 +8,7 @@ class BasicAuthRegistrationProvider(RegistrationProvider): - name = 'basic_auth' + name = "basic_auth" required_fields = ["username", "email", "password"] def validate(self, data): @@ -21,47 +21,40 @@ def validate(self, data): return {key: data[key] for key in self.required_fields} def register_user(self, username, email, password, **kwargs): - user = get_user_model()( - username=username, - email=email - ) + user = get_user_model()(username=username, email=email) try: password_validation.validate_password(password, user) except Exception: - raise FormattedException(status=HTTP_400_BAD_REQUEST, m='weak_password') + raise FormattedException(status=HTTP_400_BAD_REQUEST, m="weak_password") user.set_password(password) return user class BasicAuthLoginProvider(LoginProvider): - name = 'basic_auth' + name = "basic_auth" def login_user(self, username, password, context, **kwargs): - user = authenticate(request=context.get('request'), - username=username, password=password) + user = authenticate(request=context.get("request"), username=username, password=password) if not user: - login_reject.send(sender=self.__class__, username=username, reason='creds') - raise FormattedException(m='incorrect_username_or_password', d={'reason': 'incorrect_username_or_password'}, - status=HTTP_401_UNAUTHORIZED) + login_reject.send(sender=self.__class__, username=username, reason="creds") + raise FormattedException(m="incorrect_username_or_password", d={"reason": "incorrect_username_or_password"}, status=HTTP_401_UNAUTHORIZED) if not user.email_verified and not user.is_superuser: - login_reject.send(sender=self.__class__, username=username, reason='email') - raise FormattedException(m='email_verification_required', d={'reason': 'email_verification_required'}, - status=HTTP_401_UNAUTHORIZED) + login_reject.send(sender=self.__class__, username=username, reason="email") + raise FormattedException(m="email_verification_required", d={"reason": "email_verification_required"}, status=HTTP_401_UNAUTHORIZED) if not user.can_login(): - login_reject.send(sender=self.__class__, username=username, reason='closed') - raise FormattedException(m='login_not_open', d={'reason': 'login_not_open'}, - status=HTTP_401_UNAUTHORIZED) + login_reject.send(sender=self.__class__, username=username, reason="closed") + raise FormattedException(m="login_not_open", d={"reason": "login_not_open"}, status=HTTP_401_UNAUTHORIZED) login.send(sender=self.__class__, user=user) return user class BasicAuthTokenProvider(TokenProvider): - name = 'basic_auth' + name = "basic_auth" def issue_token(self, user, **kwargs): return user.issue_token() diff --git a/src/authentication/models.py b/src/authentication/models.py index 12be30fe..fb1c5e98 100644 --- a/src/authentication/models.py +++ b/src/authentication/models.py @@ -14,9 +14,7 @@ class Token(ExportModelOperationsMixin("token"), models.Model): key = models.CharField(max_length=40, primary_key=True) - user = models.ForeignKey( - settings.AUTH_USER_MODEL, related_name="tokens", on_delete=models.CASCADE - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="tokens", on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey( settings.AUTH_USER_MODEL, @@ -60,9 +58,7 @@ class PasswordResetToken(ExportModelOperationsMixin("password_reset_token"), mod class BackupCode(ExportModelOperationsMixin("backup_code"), models.Model): - user = models.ForeignKey( - settings.AUTH_USER_MODEL, related_name="backup_codes", on_delete=models.CASCADE - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="backup_codes", on_delete=models.CASCADE) code = models.CharField(max_length=8) class Meta: diff --git a/src/authentication/permissions.py b/src/authentication/permissions.py index ce3f5ef6..82cb24f9 100644 --- a/src/authentication/permissions.py +++ b/src/authentication/permissions.py @@ -8,5 +8,4 @@ def has_permission(self, request, view): class VerifyingTwoFactor(BasePermission): def has_permission(self, request, view): - return request.user and request.user.is_authenticated and request.user.totp_device is not None \ - and not request.user.totp_device.verified + return request.user and request.user.is_authenticated and request.user.totp_device is not None and not request.user.totp_device.verified diff --git a/src/authentication/providers.py b/src/authentication/providers.py index c8950e1f..d47174fc 100644 --- a/src/authentication/providers.py +++ b/src/authentication/providers.py @@ -9,7 +9,7 @@ class RegistrationProvider(Provider, abc.ABC): # pragma: no cover - type = 'registration' + type = "registration" @abc.abstractmethod def validate(self, data): @@ -20,21 +20,21 @@ def register_user(self, **kwargs): pass def validate_email(self, email): - allow_domain = config.config.get('email_allow') + allow_domain = config.config.get("email_allow") if allow_domain: email_validator = EmailValidator(allow_domain) else: email_validator = EmailValidator() if email_validator(email): - raise ValidationError('invalid_email') + raise ValidationError("invalid_email") def check_email_or_username_in_use(self, email=None, username=None): if get_user_model().objects.filter(username=username) or get_user_model().objects.filter(email=email): - raise ValidationError('email_or_username_in_use') + raise ValidationError("email_or_username_in_use") class LoginProvider(Provider, abc.ABC): # pragma: no cover - type = 'login' + type = "login" @abc.abstractmethod def login_user(self, **kwargs): @@ -42,7 +42,7 @@ def login_user(self, **kwargs): class TokenProvider(Provider, abc.ABC): # pragma: no cover - type = 'token' + type = "token" @abc.abstractmethod def issue_token(self, user, **kwargs): diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 287cebb0..b7d36802 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -22,8 +22,8 @@ class LoginSerializer(serializers.Serializer): password = serializers.CharField(trim_whitespace=False) def validate(self, data): - user = providers.get_provider('login').login_user(**data, context=self.context) - data['user'] = user + user = providers.get_provider("login").login_user(**data, context=self.context) + data["user"] = user return data @@ -33,19 +33,18 @@ class LoginTwoFactorSerializer(serializers.Serializer): tfa = serializers.CharField(max_length=255, allow_null=True, allow_blank=True) def validate(self, data): - user = providers.get_provider('login').login_user(**data, context=self.context) - data['user'] = user + user = providers.get_provider("login").login_user(**data, context=self.context) + data["user"] = user return data class RegistrationSerializer(serializers.Serializer): def validate(self, _): - register_end_time = config.config.get('register_end_time') - if not (config.config.get('enable_registration') and time.time() >= config.config.get('register_start_time')) \ - and (register_end_time < 0 or register_end_time > time.time()): - raise FormattedException(m='registration_not_open', status=HTTP_403_FORBIDDEN) + register_end_time = config.config.get("register_end_time") + if not (config.config.get("enable_registration") and time.time() >= config.config.get("register_start_time")) and (register_end_time < 0 or register_end_time > time.time()): + raise FormattedException(m="registration_not_open", status=HTTP_403_FORBIDDEN) - validated_data = providers.get_provider('registration').validate(self.initial_data) + validated_data = providers.get_provider("registration").validate(self.initial_data) if config.config.get("invite_required"): if not self.initial_data.get("invite", None): raise FormattedException(m="invite_required", status=HTTP_400_BAD_REQUEST) @@ -53,7 +52,7 @@ def validate(self, _): return validated_data def create(self, validated_data): - user = providers.get_provider('registration').register_user(**validated_data, context=self.context) + user = providers.get_provider("registration").register_user(**validated_data, context=self.context) if not get_user_model().objects.all().exists(): user.is_staff = True @@ -74,8 +73,7 @@ def create(self, validated_data): else: user.save() try: - send_email(user.email, 'RACTF - Verify your email', 'verify', - url=settings.FRONTEND_URL + 'verify?id={}&secret={}'.format(user.id, user.email_token)) + send_email(user.email, "RACTF - Verify your email", "verify", url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token)) except: user.delete() raise FormattedException(m="creation_failed") @@ -95,19 +93,19 @@ def create(self, validated_data): name=user.username, password=secrets.token_hex(32), ) - + user.save() register.send(sender=self.__class__, user=user) if not settings.MAIL["SEND"]: - return {"token": user.issue_token(), 'email': user.email} + return {"token": user.issue_token(), "email": user.email} else: return {} def to_representation(self, instance): representation = super(RegistrationSerializer, self).to_representation(instance) - representation.pop('password', None) + representation.pop("password", None) return representation @@ -117,14 +115,14 @@ class PasswordResetSerializer(serializers.Serializer): password = serializers.CharField() def validate(self, data): - uid = data.get('uid') - token = data.get('token') - password = data.get('password') + uid = data.get("uid") + token = data.get("token") + password = data.get("password") user = get_object_or_404(get_user_model(), id=uid) reset_token = get_object_or_404(PasswordResetToken, token=token, user_id=uid, expires__gt=timezone.now()) password_validation.validate_password(password, reset_token) - data['user'] = user - data['reset_token'] = reset_token + data["user"] = user + data["reset_token"] = reset_token return data @@ -133,12 +131,12 @@ class EmailVerificationSerializer(serializers.Serializer): token = serializers.CharField(max_length=64) def validate(self, data): - uid = int(data.get('uid')) - token = data.get('token') + uid = int(data.get("uid")) + token = data.get("token") user = get_object_or_404(get_user_model(), id=uid, email_token=token) if user.email_verified: - raise FormattedException(m='email_already_verified', status=HTTP_403_FORBIDDEN) - data['user'] = user + raise FormattedException(m="email_already_verified", status=HTTP_403_FORBIDDEN) + data["user"] = user return data @@ -146,10 +144,10 @@ class EmailSerializer(serializers.Serializer): email = serializers.EmailField() def validate(self, data): - user = get_object_or_404(get_user_model(), email=data.get('email')) + user = get_object_or_404(get_user_model(), email=data.get("email")) if user.email_verified: - raise FormattedException(m='email_already_verified', status=HTTP_403_FORBIDDEN) - data['user'] = user + raise FormattedException(m="email_already_verified", status=HTTP_403_FORBIDDEN) + data["user"] = user return data @@ -158,11 +156,11 @@ class ChangePasswordSerializer(serializers.Serializer): old_password = serializers.CharField() def validate(self, data): - user = self.context['request'].user - password = data.get('password') - old_password = data.get('old_password') + user = self.context["request"].user + password = data.get("password") + old_password = data.get("old_password") if not user.check_password(old_password): - raise FormattedException(status=HTTP_401_UNAUTHORIZED, m='invalid_password') + raise FormattedException(status=HTTP_401_UNAUTHORIZED, m="invalid_password") password_validation.validate_password(password, user) return data @@ -176,7 +174,7 @@ class GenerateInvitesSerializer(serializers.Serializer): class InviteCodeSerializer(serializers.ModelSerializer): class Meta: model = InviteCode - fields = ['id', 'code', 'uses', 'max_uses', 'auto_team'] + fields = ["id", "code", "uses", "max_uses", "auto_team"] class CreateBotSerializer(serializers.Serializer): diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 9392f026..126c9006 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -3,15 +3,24 @@ import pyotp from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_403_FORBIDDEN, \ - HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED +from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED from rest_framework.test import APITestCase import config from authentication.models import PasswordResetToken, TOTPDevice, InviteCode, BackupCode, Token -from authentication.views import VerifyEmailView, DoPasswordResetView, AddTwoFactorView, VerifyTwoFactorView, LoginView, \ - RegistrationView, ChangePasswordView, LoginTwoFactorView, RequestPasswordResetView, RegenerateBackupCodesView, \ - CreateBotView +from authentication.views import ( + VerifyEmailView, + DoPasswordResetView, + AddTwoFactorView, + VerifyTwoFactorView, + LoginView, + RegistrationView, + ChangePasswordView, + LoginTwoFactorView, + RequestPasswordResetView, + RegenerateBackupCodesView, + CreateBotView, +) from team.models import Team @@ -20,138 +29,132 @@ def get_fake_time(): class RegisterTestCase(APITestCase): - def setUp(self): - RegistrationView.throttle_scope = '' + RegistrationView.throttle_scope = "" def test_register(self): data = { - 'username': 'user1', - 'password': 'uO7*$E@0ngqL', - 'email': 'user@example.org', + "username": "user1", + "password": "uO7*$E@0ngqL", + "email": "user@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_with_mail(self): - with self.settings(MAIL={ - "SEND_ADDRESS": "no-reply@ractf.co.uk", - "SEND_NAME": "RACTF", - "SEND": True, - "SEND_MODE": "SES" - }): + with self.settings(MAIL={"SEND_ADDRESS": "no-reply@ractf.co.uk", "SEND_NAME": "RACTF", "SEND": True, "SEND_MODE": "SES"}): data = { - 'username': 'user1', - 'password': 'uO7*$E@0ngqL', - 'email': 'user@example.org', + "username": "user1", + "password": "uO7*$E@0ngqL", + "email": "user@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_weak_password(self): data = { - 'username': 'user2', - 'password': 'password', - 'email': 'user2@example.org', + "username": "user2", + "password": "password", + "email": "user2@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_duplicate_username(self): data = { - 'username': 'user3', - 'password': 'uO7*$E@0ngqL', - 'email': 'user3@example.org', + "username": "user3", + "password": "uO7*$E@0ngqL", + "email": "user3@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) data = { - 'username': 'user3', - 'password': 'uO7*$E@0ngqL', - 'email': 'user4@example.org', + "username": "user3", + "password": "uO7*$E@0ngqL", + "email": "user4@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_duplicate_email(self): data = { - 'username': 'user4', - 'password': 'uO7*$E@0ngqL', - 'email': 'user4@example.org', + "username": "user4", + "password": "uO7*$E@0ngqL", + "email": "user4@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) data = { - 'username': 'user5', - 'password': 'uO7*$E@0ngqL', - 'email': 'user4@example.org', + "username": "user5", + "password": "uO7*$E@0ngqL", + "email": "user4@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) - @mock.patch('time.time', side_effect=get_fake_time) + @mock.patch("time.time", side_effect=get_fake_time) def test_register_closed(self, mock_obj): - config.config.set('enable_prelogin', False) + config.config.set("enable_prelogin", False) data = { - 'username': 'user6', - 'password': 'uO7*$E@0ngqL', - 'email': 'user6@example.org', + "username": "user6", + "password": "uO7*$E@0ngqL", + "email": "user6@example.org", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - config.config.set('enable_prelogin', True) + config.config.set("enable_prelogin", True) def test_register_admin(self): data = { - 'username': 'user6', - 'password': 'uO7*$E@0ngqL', - 'email': 'user6@example.org', + "username": "user6", + "password": "uO7*$E@0ngqL", + "email": "user6@example.org", } - response = self.client.post(reverse('register'), data) - self.assertTrue(get_user_model().objects.filter(username=data['username']).first().is_staff) + response = self.client.post(reverse("register"), data) + self.assertTrue(get_user_model().objects.filter(username=data["username"]).first().is_staff) def test_register_second(self): data = { - 'username': 'user6', - 'password': 'uO7*$E@0ngqL', - 'email': 'user6@example.org', + "username": "user6", + "password": "uO7*$E@0ngqL", + "email": "user6@example.org", } - self.client.post(reverse('register'), data) + self.client.post(reverse("register"), data) data = { - 'username': 'user7', - 'password': 'uO7*$E@0ngqL', - 'email': 'user7@example.org', + "username": "user7", + "password": "uO7*$E@0ngqL", + "email": "user7@example.org", } - response = self.client.post(reverse('register'), data) - self.assertFalse(get_user_model().objects.filter(username=data['username']).first().is_staff) + response = self.client.post(reverse("register"), data) + self.assertFalse(get_user_model().objects.filter(username=data["username"]).first().is_staff) def test_register_malformed(self): data = { - 'username': 'user6', + "username": "user6", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_invalid_email(self): data = { - 'username': 'user6', - 'password': 'uO7*$E@0ngqL', - 'email': 'user6', + "username": "user6", + "password": "uO7*$E@0ngqL", + "email": "user6", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_teams_disabled(self): - config.config.set('enable_teams', False) + config.config.set("enable_teams", False) data = { - 'username': 'user10', - 'password': 'uO7*$E@0ngqL', - 'email': 'user10@example.com', + "username": "user10", + "password": "uO7*$E@0ngqL", + "email": "user10@example.com", } - response = self.client.post(reverse('register'), data) - config.config.set('enable_teams', True) + response = self.client.post(reverse("register"), data) + config.config.set("enable_teams", True) self.assertEqual(response.status_code, HTTP_201_CREATED) - self.assertEqual(get_user_model().objects.get(username='user10').team.name, 'user10') + self.assertEqual(get_user_model().objects.get(username="user10").team.name, "user10") class EmailResendTestCase(APITestCase): @@ -159,19 +162,19 @@ def test_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): user = get_user_model()(username="test_verify_user", email_verified=False, email="tvu@example.com") user.save() - response = self.client.post(reverse('resend-email'), {"email": "tvu@example.com"}) + response = self.client.post(reverse("resend-email"), {"email": "tvu@example.com"}) self.assertEqual(response.status_code, HTTP_200_OK) def test_already_verified_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): user = get_user_model()(username="resend-email", email_verified=True, email="tvu@example.com") user.save() - response = self.client.post(reverse('resend-email'), {"email": "tvu@example.com"}) + response = self.client.post(reverse("resend-email"), {"email": "tvu@example.com"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_non_existing_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): - response = self.client.post(reverse('resend-email'), {"email": "nonexisting@example.com"}) + response = self.client.post(reverse("resend-email"), {"email": "nonexisting@example.com"}) self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) @@ -183,7 +186,7 @@ def test_sudo(self): user2.save() self.client.force_authenticate(user) - req = self.client.post(reverse('sudo'), {"id": user2.id}) + req = self.client.post(reverse("sudo"), {"id": user2.id}) self.assertEqual(req.status_code, HTTP_200_OK) @@ -192,653 +195,630 @@ def test_response_length(self): user = get_user_model()(username="resend-email", is_staff=True, email="tvu@example.com", is_superuser=True) user.save() self.client.force_authenticate(user=user) - team = Team.objects.create(owner=user, name=user.username, password='123123') - response = self.client.post(reverse('generate-invites'), {"amount": 15, "auto_team": team.id, "max_uses": 1}) + team = Team.objects.create(owner=user, name=user.username, password="123123") + response = self.client.post(reverse("generate-invites"), {"amount": 15, "auto_team": team.id, "max_uses": 1}) self.assertEqual(len(response.data["d"]["invite_codes"]), 15) def test_invites_viewset(self): user = get_user_model()(username="resend-email", is_staff=True, email="tvu@example.com", is_superuser=True) user.save() self.client.force_authenticate(user=user) - self.client.post(reverse('generate-invites'), {"amount": 15, "max_uses": 1}) - response = self.client.get(reverse('invites-list')) + self.client.post(reverse("generate-invites"), {"amount": 15, "max_uses": 1}) + response = self.client.get(reverse("invites-list")) self.assertEqual(len(response.data["d"]["results"]), 15) - class InviteRequiredRegistrationTestCase(APITestCase): - def setUp(self): - RegistrationView.throttle_scope = '' - config.config.set('invite_required', True) - InviteCode(code='test1', max_uses=10).save() - InviteCode(code='test2', max_uses=1).save() - InviteCode(code='test3', max_uses=1).save() - user = get_user_model()(username='invtestadmin', email='invtestadmin@example.org', email_verified=True, - is_superuser=True, is_staff=True) - user.set_password('password') + RegistrationView.throttle_scope = "" + config.config.set("invite_required", True) + InviteCode(code="test1", max_uses=10).save() + InviteCode(code="test2", max_uses=1).save() + InviteCode(code="test3", max_uses=1).save() + user = get_user_model()(username="invtestadmin", email="invtestadmin@example.org", email_verified=True, is_superuser=True, is_staff=True) + user.set_password("password") user.save() self.user = user - team = Team(name='team', password='password', owner=user) + team = Team(name="team", password="password", owner=user) team.save() self.team = team - InviteCode(code='test4', max_uses=1, auto_team=team).save() + InviteCode(code="test4", max_uses=1, auto_team=team).save() def tearDown(self): - config.config.set('invite_required', False) + config.config.set("invite_required", False) def test_register_invite_required_missing_invite(self): data = { - 'username': 'user7', - 'password': 'uO7*$E@0ngqL', - 'email': 'user7@example.com', + "username": "user7", + "password": "uO7*$E@0ngqL", + "email": "user7@example.com", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_invite_required_valid(self): data = { - 'username': 'user8', - 'password': 'uO7*$E@0ngqL', - 'email': 'user8@example.com', - 'invite': 'test1', + "username": "user8", + "password": "uO7*$E@0ngqL", + "email": "user8@example.com", + "invite": "test1", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_invite_required_invalid(self): data = { - 'username': 'user8', - 'password': 'uO7*$E@0ngqL', - 'email': 'user8@example.com', - 'invite': 'test1---', + "username": "user8", + "password": "uO7*$E@0ngqL", + "email": "user8@example.com", + "invite": "test1---", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_register_invite_required_already_used(self): data = { - 'username': 'user9', - 'password': 'uO7*$E@0ngqL', - 'email': 'user9@example.com', - 'invite': 'test2', + "username": "user9", + "password": "uO7*$E@0ngqL", + "email": "user9@example.com", + "invite": "test2", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) data = { - 'username': 'user10', - 'password': 'uO7*$E@0ngqL', - 'email': 'user10@example.com', - 'invite': 'test2', + "username": "user10", + "password": "uO7*$E@0ngqL", + "email": "user10@example.com", + "invite": "test2", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_register_invite_required_valid_maxing_uses(self): data = { - 'username': 'user11', - 'password': 'uO7*$E@0ngqL', - 'email': 'user11@example.com', - 'invite': 'test3', + "username": "user11", + "password": "uO7*$E@0ngqL", + "email": "user11@example.com", + "invite": "test3", } - response = self.client.post(reverse('register'), data) + response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_register_invite_required_auto_team(self): data = { - 'username': 'user12', - 'password': 'uO7*$E@0ngqL', - 'email': 'user12@example.com', - 'invite': 'test4', + "username": "user12", + "password": "uO7*$E@0ngqL", + "email": "user12@example.com", + "invite": "test4", } - response = self.client.post(reverse('register'), data) - self.assertEqual(get_user_model().objects.get(username='user12').team.id, self.team.id) + response = self.client.post(reverse("register"), data) + self.assertEqual(get_user_model().objects.get(username="user12").team.id, self.team.id) class LogoutTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='logout-test', email='logout-test@example.org') - user.set_password('password') + user = get_user_model()(username="logout-test", email="logout-test@example.org") + user.set_password("password") user.email_verified = True user.save() self.user = user def test_logout(self): - self.client.post(reverse('login'), data={'username': self.user.username, 'password': 'password', 'otp': ''}) + self.client.post(reverse("login"), data={"username": self.user.username, "password": "password", "otp": ""}) self.client.force_authenticate(user=self.user) - response = self.client.post(reverse('logout')) + response = self.client.post(reverse("logout")) self.assertEqual(response.status_code, HTTP_200_OK) def test_logout_not_logged_in(self): - response = self.client.post(reverse('logout')) + response = self.client.post(reverse("logout")) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class LoginTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='login-test', email='login-test@example.org') - user.set_password('password') + user = get_user_model()(username="login-test", email="login-test@example.org") + user.set_password("password") user.email_verified = True user.save() self.user = user - LoginView.throttle_scope = '' + LoginView.throttle_scope = "" def test_login(self): data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_login_invalid(self): data = { - 'username': 'login-test', - 'password': 'a', + "username": "login-test", + "password": "a", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_missing_data(self): data = { - 'username': 'login-test', + "username": "login-test", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_email_not_verified(self): self.user.email_verified = False self.user.save() data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) - @mock.patch('time.time', side_effect=get_fake_time) + @mock.patch("time.time", side_effect=get_fake_time) def test_login_login_closed(self, mock_obj): data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - config.config.set('enable_prelogin', False) - response = self.client.post(reverse('login'), data) - config.config.set('enable_prelogin', True) + config.config.set("enable_prelogin", False) + response = self.client.post(reverse("login"), data) + config.config.set("enable_prelogin", True) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_inactive(self): self.user.is_active = False self.user.save() data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) self.user.is_active = True self.user.save() def test_login_with_email(self): data = { - 'username': 'login-test@example.org', - 'password': 'password', - 'otp': '', + "username": "login-test@example.org", + "password": "password", + "otp": "", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_login_wrong_user(self): data = { - 'username': 'login-', - 'password': 'password', - 'otp': '', + "username": "login-", + "password": "password", + "otp": "", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_wrong_password(self): data = { - 'username': 'login-test', - 'password': 'passw', - 'otp': '', + "username": "login-test", + "password": "passw", + "otp": "", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_malformed(self): data = { - 'username': 'login-test', - 'otp': '', + "username": "login-test", + "otp": "", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_2fa_required(self): TOTPDevice(user=self.user, verified=True).save() data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - response = self.client.post(reverse('login'), data) + response = self.client.post(reverse("login"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class Login2FATestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='login-test', email='login-test@example.org') - user.set_password('password') + user = get_user_model()(username="login-test", email="login-test@example.org") + user.set_password("password") user.email_verified = True user.save() TOTPDevice(user=user, verified=True).save() self.user = user - LoginTwoFactorView.throttle_scope = '' + LoginTwoFactorView.throttle_scope = "" def test_login_2fa(self): secret = TOTPDevice.objects.get(user=self.user).totp_secret totp = pyotp.TOTP(secret) data = { - 'username': 'login-test', - 'password': 'password', - 'tfa': totp.now(), + "username": "login-test", + "password": "password", + "tfa": totp.now(), } - response = self.client.post(reverse('login-2fa'), data) + response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_login_2fa_invalid(self): data = { - 'username': 'login-test', - 'password': 'password', - 'tfa': '123456', + "username": "login-test", + "password": "password", + "tfa": "123456", } - response = self.client.post(reverse('login-2fa'), data) + response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_2fa_without_2fa(self): - user = get_user_model()(username='login-test-no-2fa', email='login-test-no-2fa@example.org') - user.set_password('password') + user = get_user_model()(username="login-test-no-2fa", email="login-test-no-2fa@example.org") + user.set_password("password") user.email_verified = True user.save() - data = { - 'username': 'login-test-no-2fa', - 'password': 'password', - 'tfa': '123456' - } - response = self.client.post(reverse('login-2fa'), data) + data = {"username": "login-test-no-2fa", "password": "password", "tfa": "123456"} + response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_2fa_missing(self): data = { - 'username': 'login-test', - 'password': 'password', + "username": "login-test", + "password": "password", } - response = self.client.post(reverse('login-2fa'), data) + response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_login_2fa_backup_cde(self): - BackupCode(user=self.user, code='12345678').save() + BackupCode(user=self.user, code="12345678").save() data = { - 'username': 'login-test', - 'password': 'password', - 'tfa': '12345678', + "username": "login-test", + "password": "password", + "tfa": "12345678", } - response = self.client.post(reverse('login-2fa'), data) + response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_200_OK) class TokenTestCase(APITestCase): def test_token_str(self): - user = get_user_model()(username='token-test', email='token-test@example.org') + user = get_user_model()(username="token-test", email="token-test@example.org") user.save() - tok = Token(key="a"*40, user=user) - self.assertEqual(str(tok), "a"*40) + tok = Token(key="a" * 40, user=user) + self.assertEqual(str(tok), "a" * 40) class TFATestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='2fa-test', email='2fa-test@example.org') - user.set_password('password') + user = get_user_model()(username="2fa-test", email="2fa-test@example.org") + user.set_password("password") user.email_verified = True user.save() self.user = user - AddTwoFactorView.throttle_scope = '' - VerifyTwoFactorView.throttle_scope = '' + AddTwoFactorView.throttle_scope = "" + VerifyTwoFactorView.throttle_scope = "" def test_add_2fa_unauthenticated(self): - response = self.client.post(reverse('add-2fa')) + response = self.client.post(reverse("add-2fa")) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) self.assertFalse(self.user.has_2fa()) def test_add_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) self.assertFalse(self.user.has_2fa()) self.assertNotEqual(self.user.totp_device, None) def test_add_2fa_twice(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) - response = self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) + response = self.client.post(reverse("add-2fa")) self.assertEqual(response.status_code, HTTP_200_OK) def test_verify_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) secret = self.user.totp_device.totp_secret totp = pyotp.TOTP(secret) - self.client.post(reverse('verify-2fa'), data={'otp': totp.now()}) + self.client.post(reverse("verify-2fa"), data={"otp": totp.now()}) self.assertTrue(self.user.has_2fa()) def test_verify_2fa_invalid(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) - self.client.post(reverse('verify-2fa'), data={'otp': '123456'}) + self.client.post(reverse("add-2fa")) + self.client.post(reverse("verify-2fa"), data={"otp": "123456"}) self.assertFalse(self.user.totp_device.verified) def test_add_2fa_with_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) secret = self.user.totp_device.totp_secret totp = pyotp.TOTP(secret) - self.client.post(reverse('verify-2fa'), data={'otp': totp.now()}) - response = self.client.post(reverse('add-2fa')) + self.client.post(reverse("verify-2fa"), data={"otp": totp.now()}) + response = self.client.post(reverse("add-2fa")) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_remove_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) totp_device = get_user_model().objects.get(id=self.user.id).totp_device totp_device.verified = True totp_device.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) - response = self.client.post(reverse('remove-2fa'), data={ - 'otp': pyotp.TOTP(totp_device.totp_secret).now() - }) + response = self.client.post(reverse("remove-2fa"), data={"otp": pyotp.TOTP(totp_device.totp_secret).now()}) self.assertEqual(response.status_code, HTTP_200_OK) def test_remove_2fa_fail(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) totp_device = get_user_model().objects.get(id=self.user.id).totp_device totp_device.verified = True totp_device.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) - response = self.client.post(reverse('remove-2fa'), data={ - 'otp': "invalid_otp" - }) + response = self.client.post(reverse("remove-2fa"), data={"otp": "invalid_otp"}) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_remove_2fa_removes_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) totp_device = get_user_model().objects.get(id=self.user.id).totp_device totp_device.verified = True totp_device.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) - response = self.client.post(reverse('remove-2fa'), data={ - 'otp': pyotp.TOTP(totp_device.totp_secret).now() - }) + response = self.client.post(reverse("remove-2fa"), data={"otp": pyotp.TOTP(totp_device.totp_secret).now()}) self.assertFalse(get_user_model().objects.get(id=self.user.id).has_2fa()) def test_remove_2fa_no_2fa(self): self.client.force_authenticate(user=self.user) - self.client.post(reverse('add-2fa')) + self.client.post(reverse("add-2fa")) user = get_user_model().objects.get(id=self.user.id) user.totp_device = None user.save() - response = self.client.post(reverse('remove-2fa')) + response = self.client.post(reverse("remove-2fa")) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) class RequestPasswordResetTestCase(APITestCase): - def setUp(self): - RequestPasswordResetView.throttle_scope = '' + RequestPasswordResetView.throttle_scope = "" def test_password_reset_request_invalid(self): - response = self.client.post(reverse('request-password-reset'), data={'email': 'user10@example.org'}) + response = self.client.post(reverse("request-password-reset"), data={"email": "user10@example.org"}) self.assertEqual(response.status_code, HTTP_200_OK) def test_password_reset_request_valid(self): - with self.settings(MAIL={ - "SEND_ADDRESS": "no-reply@ractf.co.uk", - "SEND_NAME": "RACTF", - "SEND": True, - "SEND_MODE": "SES" - }): - get_user_model()(username='test-password-rest', email='user10@example.org', email_verified=True).save() - response = self.client.post(reverse('request-password-reset'), data={'email': 'user10@example.org'}) + with self.settings(MAIL={"SEND_ADDRESS": "no-reply@ractf.co.uk", "SEND_NAME": "RACTF", "SEND": True, "SEND_MODE": "SES"}): + get_user_model()(username="test-password-rest", email="user10@example.org", email_verified=True).save() + response = self.client.post(reverse("request-password-reset"), data={"email": "user10@example.org"}) self.assertEqual(response.status_code, HTTP_200_OK) class DoPasswordResetTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='pr-test', email='pr-test@example.org') - user.set_password('password') + user = get_user_model()(username="pr-test", email="pr-test@example.org") + user.set_password("password") user.email_verified = True user.save() - PasswordResetToken(user=user, token='testtoken').save() + PasswordResetToken(user=user, token="testtoken").save() self.user = user - DoPasswordResetView.throttle_scope = '' + DoPasswordResetView.throttle_scope = "" def test_password_reset(self): data = { - 'uid': self.user.id, - 'token': 'testtoken', - 'password': 'uO7*$E@0ngqL', + "uid": self.user.id, + "token": "testtoken", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('do-password-reset'), data) + response = self.client.post(reverse("do-password-reset"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_password_reset_issues_token(self): data = { - 'uid': self.user.id, - 'token': 'testtoken', - 'password': 'uO7*$E@0ngqL', + "uid": self.user.id, + "token": "testtoken", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('do-password-reset'), data) - self.assertTrue('token' in response.data['d']) + response = self.client.post(reverse("do-password-reset"), data) + self.assertTrue("token" in response.data["d"]) def test_password_reset_bad_token(self): data = { - 'uid': self.user.id, - 'token': 'abc', - 'password': 'uO7*$E@0ngqL', + "uid": self.user.id, + "token": "abc", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('do-password-reset'), data) + response = self.client.post(reverse("do-password-reset"), data) self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_password_reset_weak_password(self): data = { - 'uid': self.user.id, - 'token': 'testtoken', - 'password': 'password', + "uid": self.user.id, + "token": "testtoken", + "password": "password", } - response = self.client.post(reverse('do-password-reset'), data) + response = self.client.post(reverse("do-password-reset"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_password_reset_login_disabled(self): - config.config.set('enable_login', False) + config.config.set("enable_login", False) data = { - 'uid': self.user.id, - 'token': 'testtoken', - 'password': 'uO7*$E@0ngqL', + "uid": self.user.id, + "token": "testtoken", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('do-password-reset'), data) - config.config.set('enable_login', True) - self.assertFalse('token' in response.data['d']) + response = self.client.post(reverse("do-password-reset"), data) + config.config.set("enable_login", True) + self.assertFalse("token" in response.data["d"]) - @mock.patch('time.time', side_effect=get_fake_time) + @mock.patch("time.time", side_effect=get_fake_time) def test_password_reset_cant_login_yet(self, obj): - config.config.set('enable_prelogin', False) + config.config.set("enable_prelogin", False) data = { - 'uid': self.user.id, - 'token': 'testtoken', - 'password': 'uO7*$E@0ngqL', + "uid": self.user.id, + "token": "testtoken", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('do-password-reset'), data) - config.config.set('enable_prelogin', True) - self.assertFalse('token' in response.data['d']) + response = self.client.post(reverse("do-password-reset"), data) + config.config.set("enable_prelogin", True) + self.assertFalse("token" in response.data["d"]) class VerifyEmailTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='ev-test', email='ev-test@example.org') - user.set_password('password') + user = get_user_model()(username="ev-test", email="ev-test@example.org") + user.set_password("password") user.save() self.user = user - VerifyEmailView.throttle_scope = '' + VerifyEmailView.throttle_scope = "" def test_email_verify(self): data = { - 'uid': self.user.id, - 'token': self.user.email_token, + "uid": self.user.id, + "token": self.user.email_token, } - response = self.client.post(reverse('verify-email'), data) + response = self.client.post(reverse("verify-email"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_email_verify_invalid(self): data = { - 'uid': 123, - 'token': 'haha brr', + "uid": 123, + "token": "haha brr", } - response = self.client.post(reverse('verify-email'), data) + response = self.client.post(reverse("verify-email"), data) self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_email_verify_nologin(self): config.config.set("enable_login", False) data = { - 'uid': self.user.id, - 'token': self.user.email_token, + "uid": self.user.id, + "token": self.user.email_token, } - response = self.client.post(reverse('verify-email'), data) + response = self.client.post(reverse("verify-email"), data) config.config.set("enable_login", False) self.assertEqual(response.data["d"], "") def test_email_verify_twice(self): data = { - 'uid': self.user.id, - 'token': self.user.email_token, + "uid": self.user.id, + "token": self.user.email_token, } - response = self.client.post(reverse('verify-email'), data) - response = self.client.post(reverse('verify-email'), data) + response = self.client.post(reverse("verify-email"), data) + response = self.client.post(reverse("verify-email"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_email_verify_bad_token(self): data = { - 'uid': self.user.id, - 'token': 'abc', + "uid": self.user.id, + "token": "abc", } - response = self.client.post(reverse('verify-email'), data) + response = self.client.post(reverse("verify-email"), data) self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) class ChangePasswordTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='cp-test', email='cp-test@example.org') - user.set_password('password') + user = get_user_model()(username="cp-test", email="cp-test@example.org") + user.set_password("password") user.save() self.user = user - ChangePasswordView.throttle_scope = '' + ChangePasswordView.throttle_scope = "" def test_change_password(self): self.client.force_authenticate(user=self.user) data = { - 'old_password': 'password', - 'password': 'uO7*$E@0ngqL', + "old_password": "password", + "password": "uO7*$E@0ngqL", } - response = self.client.post(reverse('change-password'), data) + response = self.client.post(reverse("change-password"), data) self.assertEqual(response.status_code, HTTP_200_OK) def test_change_password_weak(self): self.client.force_authenticate(user=self.user) data = { - 'old_password': 'password', - 'password': 'password', + "old_password": "password", + "password": "password", } - response = self.client.post(reverse('change-password'), data) + response = self.client.post(reverse("change-password"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_change_password_invalid_old(self): self.client.force_authenticate(user=self.user) data = { - 'old_password': 'passwordddddddd', - 'password': 'password', + "old_password": "passwordddddddd", + "password": "password", } - response = self.client.post(reverse('change-password'), data) + response = self.client.post(reverse("change-password"), data) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) class RegerateBackupCodesTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='backupcode-test', email='backupcode-test@example.org') - user.set_password('password') + user = get_user_model()(username="backupcode-test", email="backupcode-test@example.org") + user.set_password("password") user.save() TOTPDevice(user=user, verified=True).save() self.user = user - RegenerateBackupCodesView.throttle_scope = '' + RegenerateBackupCodesView.throttle_scope = "" def test_regenerate_backup_codes_count(self): self.client.force_authenticate(user=self.user) - response = self.client.post(reverse('regenerate-backup-codes')) - self.assertEqual(len(response.data['d']['backup_codes']), 10) + response = self.client.post(reverse("regenerate-backup-codes")) + self.assertEqual(len(response.data["d"]["backup_codes"]), 10) def test_regenerate_backup_codes_length(self): self.client.force_authenticate(user=self.user) - response = self.client.post(reverse('regenerate-backup-codes')) - self.assertEqual(sum([len(x) for x in response.data['d']['backup_codes']]), 80) + response = self.client.post(reverse("regenerate-backup-codes")) + self.assertEqual(sum([len(x) for x in response.data["d"]["backup_codes"]]), 80) def test_regenerate_backup_codes_unique(self): self.client.force_authenticate(user=self.user) - first_response = self.client.post(reverse('regenerate-backup-codes')) - second_response = self.client.post(reverse('regenerate-backup-codes')) - self.assertFalse(set(first_response.data['d']['backup_codes']) & set(second_response.data['d']['backup_codes'])) + first_response = self.client.post(reverse("regenerate-backup-codes")) + second_response = self.client.post(reverse("regenerate-backup-codes")) + self.assertFalse(set(first_response.data["d"]["backup_codes"]) & set(second_response.data["d"]["backup_codes"])) def test_regenerate_backup_codes_no_2fa(self): user = get_user_model().objects.get(id=self.user.id) user.totp_device.delete() user.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) - response = self.client.post(reverse('regenerate-backup-codes')) + response = self.client.post(reverse("regenerate-backup-codes")) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) class CreateBotUserTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='bot-test', email='bot-test@example.org', is_staff=True, - is_superuser=True) - user.set_password('password') + user = get_user_model()(username="bot-test", email="bot-test@example.org", is_staff=True, is_superuser=True) + user.set_password("password") user.save() self.user = user - CreateBotView.throttle_scope = '' + CreateBotView.throttle_scope = "" def test_unauthenticated(self): - response = self.client.post(reverse('create-bot'), data={ - 'username': 'bottest', - 'is_visible': False, - 'is_staff': True, - 'is_superuser': True, - }) + response = self.client.post( + reverse("create-bot"), + data={ + "username": "bottest", + "is_visible": False, + "is_staff": True, + "is_superuser": True, + }, + ) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authenticated_admin(self): self.client.force_authenticate(self.user) - response = self.client.post(reverse('create-bot'), data={ - 'username': 'bottest', - 'is_visible': False, - 'is_staff': True, - 'is_superuser': True, - }) + response = self.client.post( + reverse("create-bot"), + data={ + "username": "bottest", + "is_visible": False, + "is_staff": True, + "is_superuser": True, + }, + ) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_authenticated_not_admin(self): @@ -846,12 +826,15 @@ def test_authenticated_not_admin(self): self.user.is_superuser = False self.user.save() self.client.force_authenticate(self.user) - response = self.client.post(reverse('create-bot'), data={ - 'username': 'bottest', - 'is_visible': False, - 'is_staff': True, - 'is_superuser': True, - }) + response = self.client.post( + reverse("create-bot"), + data={ + "username": "bottest", + "is_visible": False, + "is_staff": True, + "is_superuser": True, + }, + ) self.user.is_staff = True self.user.is_superuser = True self.user.save() @@ -859,10 +842,13 @@ def test_authenticated_not_admin(self): def test_issues_token(self): self.client.force_authenticate(self.user) - response = self.client.post(reverse('create-bot'), data={ - 'username': 'bottest', - 'is_visible': False, - 'is_staff': True, - 'is_superuser': True, - }) - self.assertTrue('token' in response.data['d']) + response = self.client.post( + reverse("create-bot"), + data={ + "username": "bottest", + "is_visible": False, + "is_staff": True, + "is_superuser": True, + }, + ) + self.assertTrue("token" in response.data["d"]) diff --git a/src/authentication/urls.py b/src/authentication/urls.py index b16dddd7..28320c79 100644 --- a/src/authentication/urls.py +++ b/src/authentication/urls.py @@ -4,25 +4,25 @@ from authentication import views router = DefaultRouter() -router.register(r'', views.InviteViewSet, basename='invites') +router.register(r"", views.InviteViewSet, basename="invites") urlpatterns = [ - path('login/', views.LoginView.as_view(), name='login'), - path('register/', views.RegistrationView.as_view(), name='register'), - path('add_2fa/', views.AddTwoFactorView.as_view(), name='add-2fa'), - path('verify_2fa/', views.VerifyTwoFactorView.as_view(), name='verify-2fa'), - path('remove_2fa/', views.RemoveTwoFactorView.as_view(), name='remove-2fa'), - path('login_2fa/', views.LoginTwoFactorView.as_view(), name='login-2fa'), - path('logout/', views.LogoutView.as_view(), name='logout'), - path('request_password_reset/', views.RequestPasswordResetView.as_view(), name='request-password-reset'), - path('password_reset/', views.DoPasswordResetView.as_view(), name='do-password-reset'), - path('verify_email/', views.VerifyEmailView.as_view(), name='verify-email'), - path('resend_email/', views.ResendEmailView.as_view(), name='resend-email'), - path('change_password/', views.ChangePasswordView.as_view(), name='change-password'), - path('generate_invites/', views.GenerateInvitesView.as_view(), name='generate-invites'), - path('invites/', include(router.urls), name='invites'), - path('regenerate_backup_codes/', views.RegenerateBackupCodesView.as_view(), name='regenerate-backup-codes'), - path('create_bot/', views.CreateBotView.as_view(), name='create-bot'), - path('sudo/', views.SudoView.as_view(), name='sudo'), - path('desudo/', views.DesudoView.as_view(), name='desudo'), + path("login/", views.LoginView.as_view(), name="login"), + path("register/", views.RegistrationView.as_view(), name="register"), + path("add_2fa/", views.AddTwoFactorView.as_view(), name="add-2fa"), + path("verify_2fa/", views.VerifyTwoFactorView.as_view(), name="verify-2fa"), + path("remove_2fa/", views.RemoveTwoFactorView.as_view(), name="remove-2fa"), + path("login_2fa/", views.LoginTwoFactorView.as_view(), name="login-2fa"), + path("logout/", views.LogoutView.as_view(), name="logout"), + path("request_password_reset/", views.RequestPasswordResetView.as_view(), name="request-password-reset"), + path("password_reset/", views.DoPasswordResetView.as_view(), name="do-password-reset"), + path("verify_email/", views.VerifyEmailView.as_view(), name="verify-email"), + path("resend_email/", views.ResendEmailView.as_view(), name="resend-email"), + path("change_password/", views.ChangePasswordView.as_view(), name="change-password"), + path("generate_invites/", views.GenerateInvitesView.as_view(), name="generate-invites"), + path("invites/", include(router.urls), name="invites"), + path("regenerate_backup_codes/", views.RegenerateBackupCodesView.as_view(), name="regenerate-backup-codes"), + path("create_bot/", views.CreateBotView.as_view(), name="create-bot"), + path("sudo/", views.SudoView.as_view(), name="sudo"), + path("desudo/", views.DesudoView.as_view(), name="desudo"), ] diff --git a/src/authentication/views.py b/src/authentication/views.py index a3ebc183..ffcfe32c 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -17,18 +17,28 @@ from authentication import serializers from authentication.models import InviteCode, PasswordResetToken, TOTPDevice, BackupCode from authentication.permissions import HasTwoFactor, VerifyingTwoFactor -from authentication.serializers import RegistrationSerializer, EmailVerificationSerializer, ChangePasswordSerializer, \ - GenerateInvitesSerializer, InviteCodeSerializer, EmailSerializer, CreateBotSerializer +from authentication.serializers import ( + RegistrationSerializer, + EmailVerificationSerializer, + ChangePasswordSerializer, + GenerateInvitesSerializer, + InviteCodeSerializer, + EmailSerializer, + CreateBotSerializer, +) from backend.mail import send_email from backend.permissions import IsBot, IsSudo from backend.response import FormattedResponse -from backend.signals import logout, add_2fa, verify_2fa, password_reset_start, password_reset_start_reject, \ - email_verified, change_password, password_reset, remove_2fa +from backend.signals import logout, add_2fa, verify_2fa, password_reset_start, password_reset_start_reject, email_verified, change_password, password_reset, remove_2fa from backend.viewsets import AdminListModelViewSet from plugins import providers from team.models import Team -hide_password = method_decorator(sensitive_post_parameters("password", )) +hide_password = method_decorator( + sensitive_post_parameters( + "password", + ) +) class LoginView(APIView): @@ -41,15 +51,15 @@ def dispatch(self, *args, **kwargs): return super(LoginView, self).dispatch(*args, **kwargs) def post(self, request, *args, **kwargs): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.serializer_class(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) - user = serializer.validated_data['user'] + user = serializer.validated_data["user"] if user.has_2fa(): - return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={'reason': '2fa_required'}, m='2fa_required') + return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={"reason": "2fa_required"}, m="2fa_required") - token = providers.get_provider('token').issue_token(user) - return FormattedResponse({'token': token}) + token = providers.get_provider("token").issue_token(user) + return FormattedResponse({"token": token}) class RegistrationView(CreateAPIView): @@ -104,16 +114,12 @@ class RemoveTwoFactorView(APIView): throttle_scope = "2fa" def post(self, request): - code = request.data['otp'] + code = request.data["otp"] if request.user.totp_device.validate_token(code): request.user.totp_device.delete() request.user.save() remove_2fa.send(sender=self.__class__, user=request.user) - send_email( - request.user.email, - "RACTF - 2FA Has Been Disabled", - "2fa_removed" - ) + send_email(request.user.email, "RACTF - 2FA Has Been Disabled", "2fa_removed") return FormattedResponse() return FormattedResponse(status=HTTP_401_UNAUTHORIZED, m="code_incorrect") @@ -128,18 +134,18 @@ def dispatch(self, *args, **kwargs): return super(LoginTwoFactorView, self).dispatch(*args, **kwargs) def issue_token(self, user): - token = providers.get_provider('token').issue_token(user) - return FormattedResponse({'token': token}) + token = providers.get_provider("token").issue_token(user) + return FormattedResponse({"token": token}) def post(self, request, *args, **kwargs): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.serializer_class(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) - user = serializer.validated_data['user'] + user = serializer.validated_data["user"] if not user.has_2fa(): - return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={'reason': '2fa_not_enabled'}, m='2fa_not_enabled') + return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={"reason": "2fa_not_enabled"}, m="2fa_not_enabled") - token = serializer.data['tfa'] + token = serializer.data["tfa"] if len(token) == 6: if user.totp_device is not None and user.totp_device.validate_token(token): @@ -150,7 +156,7 @@ def post(self, request, *args, **kwargs): code.delete() return self.issue_token(user) - return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={'reason': 'login_failed'}, m='login_failed') + return FormattedResponse(status=HTTP_401_UNAUTHORIZED, d={"reason": "login_failed"}, m="login_failed") class RegenerateBackupCodesView(APIView): @@ -205,20 +211,16 @@ def dispatch(self, *args, **kwargs): return super(DoPasswordResetView, self).dispatch(*args, **kwargs) def post(self, request): - serializer = self.serializer_class( - data=request.data, context={"request": request} - ) + serializer = self.serializer_class(data=request.data, context={"request": request}) if not serializer.is_valid(): - return FormattedResponse( - d=serializer.errors, m="bad_request", status=HTTP_400_BAD_REQUEST - ) + return FormattedResponse(d=serializer.errors, m="bad_request", status=HTTP_400_BAD_REQUEST) data = serializer.validated_data user = data["user"] password = data["password"] user.set_password(password) user.save() - data['reset_token'].delete() + data["reset_token"].delete() password_reset.send(sender=self.__class__, user=user) if user.can_login(): return FormattedResponse({"token": user.issue_token()}) @@ -232,9 +234,7 @@ class VerifyEmailView(GenericAPIView): serializer_class = EmailVerificationSerializer def post(self, request): - serializer = self.serializer_class( - data=request.data, context={"request": request} - ) + serializer = self.serializer_class(data=request.data, context={"request": request}) if not serializer.is_valid(): return FormattedResponse( m="invalid_token_or_uid", @@ -258,9 +258,7 @@ class ResendEmailView(GenericAPIView): serializer_class = EmailSerializer def post(self, request): - serializer = self.serializer_class( - data=request.data, context={"request": request} - ) + serializer = self.serializer_class(data=request.data, context={"request": request}) if not serializer.is_valid(): return FormattedResponse( m="invalid_token_or_uid", @@ -274,9 +272,8 @@ def post(self, request): d=serializer.errors, status=HTTP_400_BAD_REQUEST, ) - send_email(user.email, 'RACTF - Verify your email', 'verify', - url=settings.FRONTEND_URL + 'verify?id={}&secret={}'.format(user.id, user.email_token)) - return FormattedResponse('email_resent') + send_email(user.email, "RACTF - Verify your email", "verify", url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token)) + return FormattedResponse("email_resent") class ChangePasswordView(APIView): @@ -289,9 +286,7 @@ def dispatch(self, *args, **kwargs): return super(ChangePasswordView, self).dispatch(*args, **kwargs) def post(self, request): - serializer = self.serializer_class( - data=request.data, context={"request": request} - ) + serializer = self.serializer_class(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) user = request.user password = serializer.validated_data["password"] @@ -306,7 +301,7 @@ class GenerateInvitesView(APIView): serializer_class = GenerateInvitesSerializer def post(self, request): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.serializer_class(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) codes = [] active_codes = InviteCode.objects.count() @@ -328,10 +323,10 @@ class InviteViewSet(AdminListModelViewSet): admin_serializer_class = InviteCodeSerializer list_admin_serializer_class = InviteCodeSerializer filter_backends = [DjangoFilterBackend] - filterset_fields = ['code', 'fully_used', 'auto_team'] + filterset_fields = ["code", "fully_used", "auto_team"] def get_queryset(self): - return InviteCode.objects.order_by('id') + return InviteCode.objects.order_by("id") class CreateBotView(APIView): @@ -339,27 +334,32 @@ class CreateBotView(APIView): serializer_class = CreateBotSerializer def post(self, request): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.serializer_class(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) - bot = get_user_model()(username=serializer.data["username"], email_verified=True, - is_visible=serializer.data["is_visible"], is_staff=serializer.data["is_staff"], - is_superuser=serializer.data["is_superuser"], is_bot=True, - email=serializer.data["username"] + "@bot.ractf") + bot = get_user_model()( + username=serializer.data["username"], + email_verified=True, + is_visible=serializer.data["is_visible"], + is_staff=serializer.data["is_staff"], + is_superuser=serializer.data["is_superuser"], + is_bot=True, + email=serializer.data["username"] + "@bot.ractf", + ) bot.save() - return FormattedResponse(d={'token': bot.issue_token()}, status=HTTP_201_CREATED) + return FormattedResponse(d={"token": bot.issue_token()}, status=HTTP_201_CREATED) class SudoView(APIView): permission_classes = (permissions.IsAdminUser & ~IsBot & ~IsSudo,) def post(self, request): - id = request.data['id'] + id = request.data["id"] user = get_object_or_404(get_user_model(), id=id) - return FormattedResponse(d={'token': user.issue_token(owner=request.user)}) + return FormattedResponse(d={"token": user.issue_token(owner=request.user)}) class DesudoView(APIView): permission_classes = (IsSudo,) def post(self, request): - return FormattedResponse(d={'token': request.sudo_from.issue_token()}) + return FormattedResponse(d={"token": request.sudo_from.issue_token()}) diff --git a/src/backend/mail.py b/src/backend/mail.py index 0278b0bc..0aaec38a 100644 --- a/src/backend/mail.py +++ b/src/backend/mail.py @@ -9,6 +9,7 @@ client = import_module("boto3").client("ses") elif settings.MAIL["SEND_MODE"] == "SENDGRID": # pragma: no cover import sendgrid + sg = sendgrid.SendGridAPIClient(settings.MAIL["SENDGRID_API_KEY"]) @@ -16,61 +17,31 @@ def send_email(send_to, subject_line, template_name, **template_details): if settings.MAIL["SEND"]: # pragma: no cover if settings.MAIL["SEND_MODE"] == "AWS": # pragma: no cover client.send_email( - Destination={ - "ToAddresses": [ - send_to - ] - }, + Destination={"ToAddresses": [send_to]}, Message={ "Body": { - "Html": { - "Charset": "UTF-8", - "Data": render_to_string(template_name + ".html", template_details) - }, - "Text": { - "Charset": "UTF-8", - "Data": render_to_string(template_name + ".txt", template_details) - } + "Html": {"Charset": "UTF-8", "Data": render_to_string(template_name + ".html", template_details)}, + "Text": {"Charset": "UTF-8", "Data": render_to_string(template_name + ".txt", template_details)}, }, - "Subject": { - "Charset": "UTF-8", - "Data": subject_line - } + "Subject": {"Charset": "UTF-8", "Data": subject_line}, }, - Source=f"{settings.MAIL['SEND_NAME']} <{settings.MAIL['SEND_ADDRESS']}>" + Source=f"{settings.MAIL['SEND_NAME']} <{settings.MAIL['SEND_ADDRESS']}>", ) elif settings.MAIL["SEND_MODE"] == "SENDGRID": # pragma: no cover data = { - "personalizations": [ - { - "to": [ - { - "email": send_to - } - ], - "subject": subject_line - } - ], - "from": { - "email": settings.MAIL['SEND_ADDRESS'], - "name": settings.MAIL['SEND_NAME'] - }, + "personalizations": [{"to": [{"email": send_to}], "subject": subject_line}], + "from": {"email": settings.MAIL["SEND_ADDRESS"], "name": settings.MAIL["SEND_NAME"]}, "content": [ - { - "type": "text/plain", - "value": render_to_string(template_name + ".txt", template_details) - }, - { - "type": "text/html", - "value": render_to_string(template_name + ".html", template_details) - } - ] + {"type": "text/plain", "value": render_to_string(template_name + ".txt", template_details)}, + {"type": "text/html", "value": render_to_string(template_name + ".html", template_details)}, + ], } sg.client.mail.send.post(request_body=data) elif settings.MAIL["SEND_MODE"] == "SMTP": # pragma: no cover import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText + if settings.MAIL["SMTP_USE_SSL"]: smtp = smtplib.SMTP_SSL(settings.MAIL["SEND_SERVER"]) else: @@ -80,13 +51,13 @@ def send_email(send_to, subject_line, template_name, **template_details): smtp.login(settings.MAIL["SEND_USERNAME"], settings.MAIL["SEND_PASSWORD"]) sender = f"{settings.MAIL['SEND_NAME']} <{settings.MAIL['SEND_ADDRESS']}>" - data = MIMEMultipart('alternative') - data['To'] = send_to - data['From'] = sender - data['Subject'] = subject_line + data = MIMEMultipart("alternative") + data["To"] = send_to + data["From"] = sender + data["Subject"] = subject_line - data.attach(MIMEText(render_to_string(template_name + ".txt", template_details), 'plain')) - data.attach(MIMEText(render_to_string(template_name + ".html", template_details), 'html')) + data.attach(MIMEText(render_to_string(template_name + ".txt", template_details), "plain")) + data.attach(MIMEText(render_to_string(template_name + ".html", template_details), "html")) smtp.sendmail(sender, send_to, data.as_string()) else: diff --git a/src/backend/pagination.py b/src/backend/pagination.py index 553ff0c6..ffb634d4 100644 --- a/src/backend/pagination.py +++ b/src/backend/pagination.py @@ -7,13 +7,8 @@ class FastPagination(LimitOffsetPagination): def get_paginated_response(self, data): - return FormattedResponse(OrderedDict([ - ('next', self.format_link(self.get_next_link())), - ('previous', self.format_link(self.get_previous_link())), - ('results', data), - ('count', self.count) - ])) + return FormattedResponse(OrderedDict([("next", self.format_link(self.get_next_link())), ("previous", self.format_link(self.get_previous_link())), ("results", data), ("count", self.count)])) @staticmethod def format_link(result): - return result if result is None else result.replace('.co.uk', '.co.uk/api/v2') + return result if result is None else result.replace(".co.uk", ".co.uk/api/v2") diff --git a/src/backend/permissions.py b/src/backend/permissions.py index cc4565b2..b46d900d 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -9,11 +9,7 @@ class AdminOrReadOnlyVisible(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.user.is_staff and not request.user.should_deny_admin(): return True - return ( - request.user.is_authenticated - and obj.is_visible - and request.method in permissions.SAFE_METHODS - ) + return request.user.is_authenticated and obj.is_visible and request.method in permissions.SAFE_METHODS class AdminOrReadOnly(permissions.BasePermission): @@ -49,4 +45,4 @@ def has_permission(self, request, view): class IsSudo(permissions.BasePermission): def has_permission(self, request, view): - return hasattr(request, 'sudo') + return hasattr(request, "sudo") diff --git a/src/backend/renderers.py b/src/backend/renderers.py index cef2cb21..f315325a 100644 --- a/src/backend/renderers.py +++ b/src/backend/renderers.py @@ -2,29 +2,27 @@ class RACTFJSONRenderer(JSONRenderer): - media_type = 'application/json' - format = 'json' - charset = 'utf-8' - render_style = 'text' + media_type = "application/json" + format = "json" + charset = "utf-8" + render_style = "text" def render(self, data, accepted_media_type=None, renderer_context=None): - if renderer_context and renderer_context.get('request') and "X-Reasonable" in renderer_context.get('request').headers: - if renderer_context.get('response').status_code >= 400: + if renderer_context and renderer_context.get("request") and "X-Reasonable" in renderer_context.get("request").headers: + if renderer_context.get("response").status_code >= 400: return super(RACTFJSONRenderer, self).render(data, accepted_media_type, renderer_context) - if data is None or not isinstance(data, dict) or 's' not in data: + if data is None or not isinstance(data, dict) or "s" not in data: return super(RACTFJSONRenderer, self).render(data, accepted_media_type, renderer_context) - if data['m'] and data['d']: + if data["m"] and data["d"]: return super(RACTFJSONRenderer, self).render(data, accepted_media_type, renderer_context) - elif data['m']: - return super(RACTFJSONRenderer, self).render({"message": data['m']}, accepted_media_type, - renderer_context) - elif data['d']: - return super(RACTFJSONRenderer, self).render(data['d'], accepted_media_type, - renderer_context) + elif data["m"]: + return super(RACTFJSONRenderer, self).render({"message": data["m"]}, accepted_media_type, renderer_context) + elif data["d"]: + return super(RACTFJSONRenderer, self).render(data["d"], accepted_media_type, renderer_context) - if data is None or not isinstance(data, dict) or 's' not in data: - response = {'s': True, 'd': data, 'm': ''} + if data is None or not isinstance(data, dict) or "s" not in data: + response = {"s": True, "d": data, "m": ""} else: response = data return super(RACTFJSONRenderer, self).render(response, accepted_media_type, renderer_context) diff --git a/src/backend/response.py b/src/backend/response.py index c35792c5..a827f571 100644 --- a/src/backend/response.py +++ b/src/backend/response.py @@ -2,10 +2,8 @@ class FormattedResponse(Response): - - def __init__(self, d='', m='', s=True, status=None, template_name=None, headers=None, exception=False, - content_type=None): + def __init__(self, d="", m="", s=True, status=None, template_name=None, headers=None, exception=False, content_type=None): if status and status >= 400: s = False - data = {'s': s, 'm': m, 'd': d} + data = {"s": s, "m": m, "d": d} super(FormattedResponse, self).__init__(data, status, template_name, headers, exception, content_type) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index 51131558..827540da 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -50,40 +50,40 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "publicmedia") DEFAULT_CONFIG = { - 'config_version': 5, - 'flag_prefix': 'ractf', - 'graph_members': 10, - 'register_start_time': time.time(), - 'register_end_time': -1, - 'end_time': time.time() + 7 * 24 * 60 * 60, - 'start_time': time.time(), - 'team_size': -1, - 'email_allow': "a", - 'login_provider': 'basic_auth', - 'registration_provider': 'basic_auth', - 'token_provider': 'basic_auth', - 'enable_bot_users': True, - 'enable_caching': True, - 'enable_ctftime': True, - 'enable_flag_submission': True, - 'enable_flag_submission_after_competition': True, - 'enable_force_admin_2fa': False, - 'enable_track_incorrect_submissions': True, - 'enable_login': True, - 'enable_prelogin': True, - 'enable_maintenance_mode': False, - 'enable_registration': True, - 'enable_scoreboard': True, - 'enable_scoring': True, - 'enable_solve_broadcast': True, - 'enable_teams': True, - 'enable_team_join': True, - 'enable_view_challenges_after_competion': True, - 'enable_team_leave': False, - 'invite_required': False, - 'hide_scoreboard_at': -1, - 'setup_wizard_complete': False, - 'sensitive_fields': ['sensitive_fields', 'enable_force_admin_2fa'] + "config_version": 5, + "flag_prefix": "ractf", + "graph_members": 10, + "register_start_time": time.time(), + "register_end_time": -1, + "end_time": time.time() + 7 * 24 * 60 * 60, + "start_time": time.time(), + "team_size": -1, + "email_allow": "a", + "login_provider": "basic_auth", + "registration_provider": "basic_auth", + "token_provider": "basic_auth", + "enable_bot_users": True, + "enable_caching": True, + "enable_ctftime": True, + "enable_flag_submission": True, + "enable_flag_submission_after_competition": True, + "enable_force_admin_2fa": False, + "enable_track_incorrect_submissions": True, + "enable_login": True, + "enable_prelogin": True, + "enable_maintenance_mode": False, + "enable_registration": True, + "enable_scoreboard": True, + "enable_scoring": True, + "enable_solve_broadcast": True, + "enable_teams": True, + "enable_team_join": True, + "enable_view_challenges_after_competion": True, + "enable_team_leave": False, + "invite_required": False, + "hide_scoreboard_at": -1, + "setup_wizard_complete": False, + "sensitive_fields": ["sensitive_fields", "enable_force_admin_2fa"], } INSTALLED_APPS = [ @@ -135,7 +135,7 @@ if os.getenv("ENABLE_SILK"): INSTALLED_APPS.insert(len(INSTALLED_APPS) - 6, "silk") - MIDDLEWARE.insert(len(MIDDLEWARE) - 2, 'silk.middleware.SilkyMiddleware') + MIDDLEWARE.insert(len(MIDDLEWARE) - 2, "silk.middleware.SilkyMiddleware") SILKY_PYTHON_PROFILER = True diff --git a/src/backend/settings/local.py b/src/backend/settings/local.py index e1988e0a..306858dc 100644 --- a/src/backend/settings/local.py +++ b/src/backend/settings/local.py @@ -2,4 +2,4 @@ DOMAIN = "localhost" -MAIL["SEND"] = False \ No newline at end of file +MAIL["SEND"] = False diff --git a/src/backend/settings/staging.py b/src/backend/settings/staging.py index a2058ce8..055f5483 100644 --- a/src/backend/settings/staging.py +++ b/src/backend/settings/staging.py @@ -8,19 +8,18 @@ from sentry_sdk.integrations.django import DjangoIntegration -sentry_sdk.init( - dsn="https://965545cacdd14caca2d2a037af90e7a7@o104250.ingest.sentry.io/5374672", - integrations=[DjangoIntegration()], - send_default_pii=True -) +sentry_sdk.init(dsn="https://965545cacdd14caca2d2a037af90e7a7@o104250.ingest.sentry.io/5374672", integrations=[DjangoIntegration()], send_default_pii=True) SEND_MAIL = True -TEMPLATES.insert(0, { - "BACKEND": "django.template.backends.jinja2.Jinja2", - "DIRS": [os.path.join(BASE_DIR, 'templates')], - "APP_DIRS": True, -}) +TEMPLATES.insert( + 0, + { + "BACKEND": "django.template.backends.jinja2.Jinja2", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + }, +) MAIL = { "SEND_ADDRESS": "no-reply@ractf.co.uk", diff --git a/src/backend/settings/test.py b/src/backend/settings/test.py index 0322c362..16e80529 100644 --- a/src/backend/settings/test.py +++ b/src/backend/settings/test.py @@ -11,7 +11,7 @@ for scope in REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]: REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"][scope] = "9999999/minute" -#DATABASES = { +# DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': 'db.sqlite3', @@ -20,15 +20,15 @@ # 'HOST': '', # 'PORT': '', # }, -#} +# } -#CONFIG = { +# CONFIG = { # "BACKEND": "config.backends.DatabaseBackend", -#} +# } -#CACHES = { +# CACHES = { # 'default': { # 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 'LOCATION': 'dead-beef', # } -#} +# } diff --git a/src/backend/signals.py b/src/backend/signals.py index 3fa1a25a..2152019a 100644 --- a/src/backend/signals.py +++ b/src/backend/signals.py @@ -1,25 +1,25 @@ from django.dispatch import Signal -flag_score = Signal(providing_args=['user', 'team', 'challenge', 'flag', 'solve']) -flag_reject = Signal(providing_args=['user', 'team', 'challenge', 'flag', 'reason']) -flag_submit = Signal(providing_args=['user', 'team', 'challenge', 'flag']) -team_join = Signal(providing_args=['user', 'team']) -team_join_attempt = Signal(providing_args=['user', 'team_name']) -team_join_reject = Signal(providing_args=['user', 'team_name']) -team_create = Signal(providing_args=['team']) -use_hint = Signal(providing_args=['user', 'team', 'hint']) -logout = Signal(providing_args=['user']) -add_2fa = Signal(providing_args=['user']) -verify_2fa = Signal(providing_args=['user']) -remove_2fa = Signal(providing_args=['user']) -password_reset = Signal(providing_args=['user']) -password_reset_start = Signal(providing_args=['user']) -password_reset_start_reject = Signal(providing_args=['email']) -email_verified = Signal(providing_args=['user']) -change_password = Signal(providing_args=['user']) -login = Signal(providing_args=['user']) -login_reject = Signal(providing_args=['username', 'reason']) -register = Signal(providing_args=['user']) -register_reject = Signal(providing_args=['username', 'email']) -websocket_connect = Signal(providing_args=['channel_layer']) -websocket_disconnect = Signal(providing_args=['channel_layer']) +flag_score = Signal(providing_args=["user", "team", "challenge", "flag", "solve"]) +flag_reject = Signal(providing_args=["user", "team", "challenge", "flag", "reason"]) +flag_submit = Signal(providing_args=["user", "team", "challenge", "flag"]) +team_join = Signal(providing_args=["user", "team"]) +team_join_attempt = Signal(providing_args=["user", "team_name"]) +team_join_reject = Signal(providing_args=["user", "team_name"]) +team_create = Signal(providing_args=["team"]) +use_hint = Signal(providing_args=["user", "team", "hint"]) +logout = Signal(providing_args=["user"]) +add_2fa = Signal(providing_args=["user"]) +verify_2fa = Signal(providing_args=["user"]) +remove_2fa = Signal(providing_args=["user"]) +password_reset = Signal(providing_args=["user"]) +password_reset_start = Signal(providing_args=["user"]) +password_reset_start_reject = Signal(providing_args=["email"]) +email_verified = Signal(providing_args=["user"]) +change_password = Signal(providing_args=["user"]) +login = Signal(providing_args=["user"]) +login_reject = Signal(providing_args=["username", "reason"]) +register = Signal(providing_args=["user"]) +register_reject = Signal(providing_args=["username", "email"]) +websocket_connect = Signal(providing_args=["channel_layer"]) +websocket_disconnect = Signal(providing_args=["channel_layer"]) diff --git a/src/backend/storages.py b/src/backend/storages.py index 59a0c75a..a2437fc4 100644 --- a/src/backend/storages.py +++ b/src/backend/storages.py @@ -2,5 +2,5 @@ class PublicMediaStorage(S3Boto3Storage): - location = 'challenge-files' - default_acl = None \ No newline at end of file + location = "challenge-files" + default_acl = None diff --git a/src/backend/tests.py b/src/backend/tests.py index 6ef49004..9ab7cab9 100644 --- a/src/backend/tests.py +++ b/src/backend/tests.py @@ -3,7 +3,6 @@ class CatchAllTestCase(APITestCase): - def test_catchall_404s(self): response = self.client.get("/sdgodgsjds") self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) diff --git a/src/backend/validators.py b/src/backend/validators.py index 74423e41..203390d3 100644 --- a/src/backend/validators.py +++ b/src/backend/validators.py @@ -13,26 +13,20 @@ def printable_name(value: str) -> None: """Ensure that team names are printable.""" if not set(value) <= PRINTABLE_CHARS: raise ValidationError( - _('%(value)s contains non-printable characters.'), - params={'value': value}, + _("%(value)s contains non-printable characters."), + params={"value": value}, ) @deconstructible class NameValidator(validators.RegexValidator): regex = r"^[\w.+ -]+\Z" - message = _( - "Enter a valid name. This value may contain only letters, " - "numbers, spaces, and ./+/-/_ characters." - ) + message = _("Enter a valid name. This value may contain only letters, " "numbers, spaces, and ./+/-/_ characters.") flags = 0 @deconstructible class LenientNameValidator(validators.RegexValidator): regex = r"^[]+\Z" - message = _( - "Enter a valid name. This value may contain only letters, " - "numbers, spaces, and ./+/-/_ characters." - ) + message = _("Enter a valid name. This value may contain only letters, " "numbers, spaces, and ./+/-/_ characters.") flags = 0 diff --git a/src/backend/views.py b/src/backend/views.py index 76bef1c8..d057f7d2 100644 --- a/src/backend/views.py +++ b/src/backend/views.py @@ -4,6 +4,5 @@ class CatchAllView(TemplateView): - def get(self, request, *args, **kwargs): - return render(template_name='404.html', context={'link': settings.FRONTEND_URL}, request=request, status=404) + return render(template_name="404.html", context={"link": settings.FRONTEND_URL}, request=request, status=404) diff --git a/src/backend/viewsets.py b/src/backend/viewsets.py index fde29112..0c755639 100644 --- a/src/backend/viewsets.py +++ b/src/backend/viewsets.py @@ -3,7 +3,7 @@ def is_exporting(request): - return request.user.is_staff and (request.headers.get('exporting') or request.headers.get('x-exporting')) + return request.user.is_staff and (request.headers.get("exporting") or request.headers.get("x-exporting")) class AdminCreateModelViewSet(ModelViewSet): @@ -30,7 +30,7 @@ class AdminListModelViewSet(ModelViewSet): def get_serializer_class(self): if self.request is None: return self.admin_serializer_class - if self.action == 'list' and not is_exporting(self.request): + if self.action == "list" and not is_exporting(self.request): if self.request.user.is_staff and not self.request.user.should_deny_admin(): return self.list_admin_serializer_class return self.list_serializer_class diff --git a/src/challenge/models.py b/src/challenge/models.py index ac6b1101..d2c006b1 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -71,23 +71,19 @@ def self_check(self): elif type(self.flag_metadata) != dict: issues.append({"issue": "invalid_flag_data_type", "challenge": self.id}) else: - issues += [{ - "issue": "invalid_flag_data", - "extra": issue, - "challenge": self.id - } for issue in self.flag_plugin.self_check()] + issues += [{"issue": "invalid_flag_data", "extra": issue, "challenge": self.id} for issue in self.flag_plugin.self_check()] return issues @cached_property def flag_plugin(self): """Return the flag plugin responsible for validating flags sent to this challenge""" - return plugins.plugins['flag'][self.flag_type](self) + return plugins.plugins["flag"][self.flag_type](self) @cached_property def points_plugin(self): """Return the points plugin responsible for granting points from this challenge""" - return plugins.plugins['points'][self.points_type](self) + return plugins.plugins["points"][self.points_type](self) def is_unlocked(self, user, solves=None): if user is None: @@ -99,9 +95,7 @@ def is_unlocked(self, user, solves=None): if user.team is None: return False if solves is None: - solves = list( - user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ) + solves = list(user.team.solves.filter(correct=True).values_list("challenge", flat=True)) requirements = self.unlock_requirements state = [] if not requirements: @@ -127,9 +121,7 @@ def is_solved(self, user, solves=None): if user.team is None: return False if solves is None: - solves = list( - user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ) + solves = list(user.team.solves.filter(correct=True).values_list("challenge", flat=True)) return self.id in solves def get_solve_count(self, solve_counter): @@ -146,7 +138,7 @@ def get_unlocked_annotated_queryset(cls, user): When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ) + ), ) else: challenges = Challenge.objects.annotate( @@ -156,7 +148,7 @@ def get_unlocked_annotated_queryset(cls, user): When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), output_field=models.BooleanField(), - ) + ), ) from hint.models import Hint from hint.models import HintUse @@ -179,9 +171,7 @@ def get_unlocked_annotated_queryset(cls, user): Prefetch("file_set", queryset=File.objects.all(), to_attr="files"), Prefetch( "tag_set", - queryset=Tag.objects.all() - if time.time() > config.config.get("end_time") - else Tag.objects.filter(post_competition=False), + queryset=Tag.objects.all() if time.time() > config.config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags", ), "first_blood", @@ -209,10 +199,8 @@ def on_challenge_update(sender, instance, created, **kwargs): class Score(ExportModelOperationsMixin("score"), models.Model): - team = models.ForeignKey('team.Team', related_name="scores", on_delete=CASCADE, null=True) - user = models.ForeignKey( - get_user_model(), related_name="scores", on_delete=SET_NULL, null=True - ) + team = models.ForeignKey("team.Team", related_name="scores", on_delete=CASCADE, null=True) + user = models.ForeignKey(get_user_model(), related_name="scores", on_delete=SET_NULL, null=True) reason = models.CharField(max_length=64) points = models.IntegerField() penalty = models.IntegerField(default=0) @@ -222,11 +210,9 @@ class Score(ExportModelOperationsMixin("score"), models.Model): class Solve(ExportModelOperationsMixin("solve"), models.Model): - team = models.ForeignKey('team.Team', related_name="solves", on_delete=CASCADE, null=True) + team = models.ForeignKey("team.Team", related_name="solves", on_delete=CASCADE, null=True) challenge = models.ForeignKey(Challenge, related_name="solves", on_delete=CASCADE) - solved_by = models.ForeignKey( - get_user_model(), related_name="solves", on_delete=SET_NULL, null=True - ) + solved_by = models.ForeignKey(get_user_model(), related_name="solves", on_delete=SET_NULL, null=True) first_blood = models.BooleanField(default=False) correct = models.BooleanField(default=True) timestamp = models.DateTimeField(default=timezone.now) diff --git a/src/challenge/permissions.py b/src/challenge/permissions.py index ad3c972e..6bb6b5ca 100644 --- a/src/challenge/permissions.py +++ b/src/challenge/permissions.py @@ -8,9 +8,5 @@ class CompetitionOpen(permissions.BasePermission): def has_permission(self, request, view): return (request.user.is_staff and not request.user.should_deny_admin()) or ( - config.config.get("start_time") <= time.time() - and ( - config.config.get("enable_view_challenges_after_competion") - or time.time() <= config.config.get("end_time") - ) + config.config.get("start_time") <= time.time() and (config.config.get("enable_view_challenges_after_competion") or time.time() <= config.config.get("end_time")) ) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 1e5090c7..24b07469 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -9,22 +9,25 @@ def setup_context(context): - context.update({ - "request": context["request"], - "solve_counter": get_solve_counts(), - "votes_positive_counter": get_positive_votes(), - "votes_negative_counter": get_negative_votes(), - }) + context.update( + { + "request": context["request"], + "solve_counter": get_solve_counts(), + "votes_positive_counter": get_positive_votes(), + "votes_negative_counter": get_negative_votes(), + } + ) if context["request"].user.team is not None: - context.update({ - "solves": list( - context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True) - ), - }) + context.update( + { + "solves": list(context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True)), + } + ) class ForeignKeyField(serpy.Field): """A :class:`Field` that gets a given attribute from a foreign object.""" + def __init__(self, *args, attr_name="id", **kwargs): super(ForeignKeyField, self).__init__(*args, **kwargs) self.attr_name = attr_name @@ -37,6 +40,7 @@ def to_value(self, value): class DateTimeField(serpy.Field): """A :class:`Field` that transforms a datetime into ISO string.""" + def to_value(self, value): return value.isoformat() @@ -85,10 +89,7 @@ def get_unlock_time_surpassed(self, instance): return instance.unlock_time_surpassed def get_votes(self, instance): - return { - "positive": self.context["votes_positive_counter"].get(instance.id, 0), - "negative": self.context["votes_negative_counter"].get(instance.id, 0) - } + return {"positive": self.context["votes_positive_counter"].get(instance.id, 0), "negative": self.context["votes_negative_counter"].get(instance.id, 0)} def get_post_score_explanation(self, instance): if self.get_unlocked(instance): @@ -132,8 +133,7 @@ class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSeria class Meta: model = Challenge - fields = ['id', 'unlock_requirements', 'challenge_metadata', 'challenge_type', 'hidden', - 'unlock_time_surpassed', 'release_time'] + fields = ["id", "unlock_requirements", "challenge_metadata", "challenge_type", "hidden", "unlock_time_surpassed", "release_time"] class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): @@ -143,7 +143,7 @@ class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer) unlocked = serializers.SerializerMethodField() unlock_time_surpassed = serializers.SerializerMethodField() votes = serializers.SerializerMethodField() - first_blood_name = serializers.ReadOnlyField(source='first_blood.username') + first_blood_name = serializers.ReadOnlyField(source="first_blood.username") solve_count = serializers.SerializerMethodField() tags = NestedTagSerializer(many=True, read_only=True) post_score_explanation = serializers.SerializerMethodField() @@ -154,31 +154,42 @@ class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSeria class Meta: model = Challenge - fields = [ - "id", - "unlock_requirements", - "challenge_metadata", - "challenge_type", - "hidden", - "unlock_time_surpassed", - "release_time", - "score" - ] - + fields = ["id", "unlock_requirements", "challenge_metadata", "challenge_type", "hidden", "unlock_time_surpassed", "release_time", "score"] def __init__(self, *args, **kwargs): super(FastChallengeSerializer, self).__init__(*args, **kwargs) - if 'context' in kwargs: - self.context = kwargs['context'] - if 'solve_counter' not in self.context: + if "context" in kwargs: + self.context = kwargs["context"] + if "solve_counter" not in self.context: setup_context(self.context) class Meta: model = Challenge - fields = ['id', 'name', 'category', 'description', 'challenge_type', 'challenge_metadata', 'flag_type', - 'author', 'auto_unlock', 'score', 'unlock_requirements', 'hints', 'files', 'solved', 'unlocked', - 'first_blood', 'first_blood_name', 'solve_count', 'hidden', 'votes', 'tags', 'unlock_time_surpassed', - 'post_score_explanation'] + fields = [ + "id", + "name", + "category", + "description", + "challenge_type", + "challenge_metadata", + "flag_type", + "author", + "auto_unlock", + "score", + "unlock_requirements", + "hints", + "files", + "solved", + "unlocked", + "first_blood", + "first_blood_name", + "solve_count", + "hidden", + "votes", + "tags", + "unlock_time_surpassed", + "post_score_explanation", + ] class Meta: model = Challenge @@ -208,17 +219,12 @@ class Meta: ] def serialize(self, instance): - if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ - not instance.hidden and instance.unlock_time_surpassed: + if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and not instance.hidden and instance.unlock_time_surpassed: return super(FastChallengeSerializer, FastChallengeSerializer(instance, context=self.context)).to_value(instance) return FastLockedChallengeSerializer(instance).data def to_representation(self, instance): - if ( - instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) - and not instance.hidden - and instance.unlock_time_surpassed - ): + if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and not instance.hidden and instance.unlock_time_surpassed: return super(ChallengeSerializer, self).to_representation(instance) return LockedChallengeSerializer(instance).to_representation(instance) @@ -290,14 +296,13 @@ class FastAdminChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): def __init__(self, *args, **kwargs): super(FastAdminChallengeSerializer, self).__init__(*args, **kwargs) - if 'context' in kwargs: - self.context = kwargs['context'] - if 'solve_counter' not in self.context: + if "context" in kwargs: + self.context = kwargs["context"] + if "solve_counter" not in self.context: setup_context(self.context) def serialize(self, instance): - return super(FastAdminChallengeSerializer, FastAdminChallengeSerializer(instance, context=self.context))\ - .to_value(instance) + return super(FastAdminChallengeSerializer, FastAdminChallengeSerializer(instance, context=self.context)).to_value(instance) def to_value(self, instance): if self.many: diff --git a/src/challenge/signals.py b/src/challenge/signals.py index 81b1c4b4..c53b7e83 100644 --- a/src/challenge/signals.py +++ b/src/challenge/signals.py @@ -7,5 +7,5 @@ @receiver(post_save, sender=Challenge) def challenge_save(sender, instance, **kwargs): - new_index = caches['default'].get('challenge_mod_index', 0) + 1 - caches['default'].set('challenge_mod_index', new_index) + new_index = caches["default"].get("challenge_mod_index", 0) + 1 + caches["default"].set("challenge_mod_index", new_index) diff --git a/src/challenge/sql.py b/src/challenge/sql.py index 2a68d428..c98168f0 100644 --- a/src/challenge/sql.py +++ b/src/challenge/sql.py @@ -5,50 +5,48 @@ def get_solve_counts(): - cache = caches['default'] - solve_counts = cache.get('solve_counts') - if solve_counts is not None and config.config.get('enable_caching'): + cache = caches["default"] + solve_counts = cache.get("solve_counts") + if solve_counts is not None and config.config.get("enable_caching"): return solve_counts with connection.cursor() as cursor: - cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;') + cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;") solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('solve_counts', solve_counts, 15) + cache.set("solve_counts", solve_counts, 15) return solve_counts def get_incorrect_solve_counts(): - cache = caches['default'] - solve_counts = cache.get('incorrect_solve_counts') - if solve_counts is not None and config.config.get('enable_caching'): + cache = caches["default"] + solve_counts = cache.get("incorrect_solve_counts") + if solve_counts is not None and config.config.get("enable_caching"): return solve_counts with connection.cursor() as cursor: - cursor.execute('SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;') + cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;") solve_counts = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('incorrect_solve_counts', solve_counts, 15) + cache.set("incorrect_solve_counts", solve_counts, 15) return solve_counts def get_positive_votes(): - cache = caches['default'] - positive_votes = cache.get('positive_votes') - if positive_votes is not None and config.config.get('enable_caching'): + cache = caches["default"] + positive_votes = cache.get("positive_votes") + if positive_votes is not None and config.config.get("enable_caching"): return positive_votes with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;') + cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;") positive_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('positive_votes', cache.get('positive_votes'), 15) + cache.set("positive_votes", cache.get("positive_votes"), 15) return positive_votes def get_negative_votes(): - cache = caches['default'] - negative_votes = cache.get('negative_votes') - if negative_votes is not None and config.config.get('enable_caching'): + cache = caches["default"] + negative_votes = cache.get("negative_votes") + if negative_votes is not None and config.config.get("enable_caching"): return negative_votes with connection.cursor() as cursor: - cursor.execute( - 'SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;') + cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;") negative_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set('negative_votes', cache.get('negative_votes'), 15) - return negative_votes \ No newline at end of file + cache.set("negative_votes", cache.get("negative_votes"), 15) + return negative_votes diff --git a/src/challenge/urls.py b/src/challenge/urls.py index 54b9383c..347aa251 100644 --- a/src/challenge/urls.py +++ b/src/challenge/urls.py @@ -4,16 +4,16 @@ from challenge import views router = DefaultRouter() -router.register(r'categories', views.CategoryViewset, basename='categories') -router.register(r'files', views.FileViewSet, basename='files') -router.register(r'tags', views.TagViewSet, basename='tags') -router.register(r'scores', views.ScoresViewset, basename='scores') -router.register(r'', views.ChallengeViewset, basename='challenges') +router.register(r"categories", views.CategoryViewset, basename="categories") +router.register(r"files", views.FileViewSet, basename="files") +router.register(r"tags", views.TagViewSet, basename="tags") +router.register(r"scores", views.ScoresViewset, basename="scores") +router.register(r"", views.ChallengeViewset, basename="challenges") urlpatterns = [ - path('submit_flag/', views.FlagSubmitView.as_view(), name='submit-flag'), - path('check_flag/', views.FlagCheckView.as_view(), name='check-flag'), - path('feedback/', views.ChallengeFeedbackView.as_view(), name='submit-feedback'), - path('vote/', views.ChallengeVoteView.as_view(), name='vote'), - path('', include(router.urls)), + path("submit_flag/", views.FlagSubmitView.as_view(), name="submit-flag"), + path("check_flag/", views.FlagCheckView.as_view(), name="check-flag"), + path("feedback/", views.ChallengeFeedbackView.as_view(), name="submit-feedback"), + path("vote/", views.ChallengeVoteView.as_view(), name="vote"), + path("", include(router.urls)), ] diff --git a/src/challenge/views.py b/src/challenge/views.py index 96d1707f..5a168450 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -25,10 +25,20 @@ from challenge.models import Challenge, Category, Solve, File, ChallengeVote, ChallengeFeedback, Tag, Score from challenge.permissions import CompetitionOpen from challenge.serializers import ( - AdminCategorySerializer, FileSerializer, CreateCategorySerializer, - CreateChallengeSerializer, ChallengeFeedbackSerializer, TagSerializer, - AdminScoreSerializer, FastCategorySerializer, get_solve_counts, get_positive_votes, get_negative_votes, - FastChallengeSerializer, FastAdminChallengeSerializer, FastAdminCategorySerializer + AdminCategorySerializer, + FileSerializer, + CreateCategorySerializer, + CreateChallengeSerializer, + ChallengeFeedbackSerializer, + TagSerializer, + AdminScoreSerializer, + FastCategorySerializer, + get_solve_counts, + get_positive_votes, + get_negative_votes, + FastChallengeSerializer, + FastAdminChallengeSerializer, + FastAdminCategorySerializer, ) import config from hint.models import Hint, HintUse @@ -39,15 +49,15 @@ def get_cache_key(user): if user.team is None: - return str(caches['default'].get('challenge_mod_index', 0)) + 'categoryvs_no_team' + return str(caches["default"].get("challenge_mod_index", 0)) + "categoryvs_no_team" else: - return str(caches['default'].get('challenge_mod_index', 0)) + 'categoryvs_team_' + str(user.team.id) + return str(caches["default"].get("challenge_mod_index", 0)) + "categoryvs_team_" + str(user.team.id) class CategoryViewset(AdminCreateModelViewSet): queryset = Category.objects.all() permission_classes = (CompetitionOpen & AdminOrReadOnly,) - throttle_scope = 'challenges' + throttle_scope = "challenges" pagination_class = None serializer_class = FastCategorySerializer admin_serializer_class = FastAdminCategorySerializer @@ -57,38 +67,40 @@ def get_queryset(self): if self.request.user.is_staff and self.request.user.should_deny_admin(): return Category.objects.none() team = self.request.user.team - challenges = Challenge.objects.annotate( - unlock_time_surpassed=Case( - When(release_time__lte=timezone.now(), then=Value(True)), - default=Value(False), - output_field=models.BooleanField(), - ) - ).prefetch_related( - Prefetch('hint_set', queryset=Hint.objects.annotate( - used=Case( - When(id__in=HintUse.objects.filter(team=team).values_list('hint_id'), then=Value(True)), + challenges = ( + Challenge.objects.annotate( + unlock_time_surpassed=Case( + When(release_time__lte=timezone.now(), then=Value(True)), default=Value(False), - output_field=models.BooleanField() - )), to_attr='hints'), - Prefetch('file_set', queryset=File.objects.all(), to_attr='files'), - Prefetch('tag_set', - queryset=Tag.objects.all() if time.time() > config.config.get('end_time') else Tag.objects.filter( - post_competition=False), to_attr='tags'), - 'hint_set__uses').select_related('first_blood') + output_field=models.BooleanField(), + ) + ) + .prefetch_related( + Prefetch( + "hint_set", + queryset=Hint.objects.annotate( + used=Case(When(id__in=HintUse.objects.filter(team=team).values_list("hint_id"), then=Value(True)), default=Value(False), output_field=models.BooleanField()) + ), + to_attr="hints", + ), + Prefetch("file_set", queryset=File.objects.all(), to_attr="files"), + Prefetch("tag_set", queryset=Tag.objects.all() if time.time() > config.config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags"), + "hint_set__uses", + ) + .select_related("first_blood") + ) if self.request.user.is_staff: categories = Category.objects else: categories = Category.objects.filter(release_time__lte=timezone.now()) - qs = categories.prefetch_related( - Prefetch('category_challenges', queryset=challenges, to_attr='challenges') - ) + qs = categories.prefetch_related(Prefetch("category_challenges", queryset=challenges, to_attr="challenges")) return qs def list(self, request, *args, **kwargs): - cache = caches['default'] + cache = caches["default"] categories = cache.get(get_cache_key(request.user)) cache_hit = categories is not None - if categories is None or not config.config.get('enable_caching'): + if categories is None or not config.config.get("enable_caching"): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) categories = serializer.data @@ -99,32 +111,29 @@ def list(self, request, *args, **kwargs): positive_votes = get_positive_votes() negative_votes = get_negative_votes() for category in categories: - for challenge in category['challenges']: - challenge['votes'] = { - 'positive': positive_votes.get(challenge['id'], 0), - 'negative': negative_votes.get(challenge['id'], 0) - } - challenge['solve_count'] = solve_counts.get(challenge['id'], 0) + for challenge in category["challenges"]: + challenge["votes"] = {"positive": positive_votes.get(challenge["id"], 0), "negative": negative_votes.get(challenge["id"], 0)} + challenge["solve_count"] = solve_counts.get(challenge["id"], 0) # This is to fix an issue with django duplicating challenges on .annotate. # If you want to clean this up, good luck. for category in categories: unlocked = set() - for challenge in category['challenges']: - if 'unlocked' in challenge and challenge['unlocked']: - unlocked.add(challenge['id']) + for challenge in category["challenges"]: + if "unlocked" in challenge and challenge["unlocked"]: + unlocked.add(challenge["id"]) new_challenges = [] - for challenge in category['challenges']: - if not (('unlocked' not in challenge or not challenge['unlocked']) and challenge['id'] in unlocked): + for challenge in category["challenges"]: + if not (("unlocked" not in challenge or not challenge["unlocked"]) and challenge["id"] in unlocked): new_challenges.append(challenge) - category['challenges'] = new_challenges + category["challenges"] = new_challenges return FormattedResponse(categories) class ChallengeViewset(AdminCreateModelViewSet): queryset = Challenge.objects.all() permission_classes = (CompetitionOpen & AdminOrReadOnly,) - throttle_scope = 'challenges' + throttle_scope = "challenges" pagination_class = None serializer_class = FastChallengeSerializer admin_serializer_class = FastAdminChallengeSerializer @@ -144,15 +153,13 @@ class ScoresViewset(ModelViewSet): def recalculate_scores(self, user, team): if user: user = get_object_or_404(get_user_model(), id=user) - user.leaderboard_points = Score.objects.filter(user=user, leaderboard=True).aggregate(Sum("points"))[ - "points__sum"] or 0 + user.leaderboard_points = Score.objects.filter(user=user, leaderboard=True).aggregate(Sum("points"))["points__sum"] or 0 user.points = Score.objects.filter(user=user).aggregate(Sum("points"))["points__sum"] or 0 user.last_score = Score.objects.filter(user=user, leaderboard=True).order_by("timestamp").first().timestamp user.save() if team: team = get_object_or_404(Team, id=team) - team.leaderboard_points = Score.objects.filter(team=team, leaderboard=True).aggregate(Sum("points"))[ - "points__sum"] or 0 + team.leaderboard_points = Score.objects.filter(team=team, leaderboard=True).aggregate(Sum("points"))["points__sum"] or 0 team.points = Score.objects.filter(team=team).aggregate(Sum("points"))["points__sum"] or 0 team.last_score = Score.objects.filter(team=team, leaderboard=True).order_by("timestamp").first().timestamp team.save() @@ -189,76 +196,73 @@ def get(self, request): return FormattedResponse(ChallengeFeedbackSerializer(feedback.filter(user=request.user).first()).data) def post(self, request): - challenge = get_object_or_404(Challenge, id=request.data.get('challenge')) + challenge = get_object_or_404(Challenge, id=request.data.get("challenge")) solve_set = Solve.objects.filter(challenge=challenge) if not solve_set.filter(team=request.user.team, correct=True).exists(): - return FormattedResponse(m='challenge_not_solved', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="challenge_not_solved", status=HTTP_403_FORBIDDEN) current_feedback = ChallengeFeedback.objects.filter(user=request.user, challenge=challenge) if current_feedback.exists(): current_feedback.delete() ChallengeFeedback(user=request.user, challenge=challenge, feedback=request.data.get("feedback")).save() - return FormattedResponse(m='feedback_recorded') + return FormattedResponse(m="feedback_recorded") class ChallengeVoteView(APIView): permission_classes = (IsAuthenticated & HasTeam & ~IsBot,) def post(self, request): - challenge = get_object_or_404(Challenge, id=request.data.get('challenge')) + challenge = get_object_or_404(Challenge, id=request.data.get("challenge")) solve_set = Solve.objects.filter(challenge=challenge) if not solve_set.filter(team=request.user.team, correct=True).exists(): - return FormattedResponse(m='challenge_not_solved', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="challenge_not_solved", status=HTTP_403_FORBIDDEN) current_vote = ChallengeVote.objects.filter(user=request.user, challenge=challenge) if current_vote.exists(): current_vote.delete() ChallengeVote(user=request.user, challenge=challenge, positive=request.data.get("positive")).save() - return FormattedResponse(m='vote_recorded') + return FormattedResponse(m="vote_recorded") class FlagSubmitView(APIView): permission_classes = (CompetitionOpen & IsAuthenticated & HasTeam & ~IsBot,) - throttle_scope = 'flag_submit' + throttle_scope = "flag_submit" def post(self, request): - if not config.config.get('enable_flag_submission') or \ - (not config.config.get('enable_flag_submission_after_competition') and time.time() > config.config.get('end_time')): - return FormattedResponse(m='flag_submission_disabled', status=HTTP_403_FORBIDDEN) + if not config.config.get("enable_flag_submission") or (not config.config.get("enable_flag_submission_after_competition") and time.time() > config.config.get("end_time")): + return FormattedResponse(m="flag_submission_disabled", status=HTTP_403_FORBIDDEN) with transaction.atomic(): team = Team.objects.select_for_update().get(id=request.user.team.id) user = get_user_model().objects.select_for_update().get(id=request.user.id) - flag = request.data.get('flag') - challenge_id = request.data.get('challenge') + flag = request.data.get("flag") + challenge_id = request.data.get("challenge") if not flag or not challenge_id: - return FormattedResponse(status=HTTP_400_BAD_REQUEST, m='No flag or challenge ID provided') + return FormattedResponse(status=HTTP_400_BAD_REQUEST, m="No flag or challenge ID provided") challenge = get_object_or_404(Challenge.objects.select_for_update(), id=challenge_id) solve_set = Solve.objects.filter(challenge=challenge) if solve_set.filter(team=team, correct=True).exists(): - return FormattedResponse(m='already_solved_challenge', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="already_solved_challenge", status=HTTP_403_FORBIDDEN) if not challenge.is_unlocked(user): - return FormattedResponse(m='challenge_not_unlocked', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="challenge_not_unlocked", status=HTTP_403_FORBIDDEN) if challenge.challenge_metadata.get("attempt_limit"): count = solve_set.filter(team=team).count() - if count > challenge.challenge_metadata['attempt_limit']: - flag_reject.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag, - reason='attempt_limit_reached') - return FormattedResponse(d={'correct': False}, m='attempt_limit_reached') + if count > challenge.challenge_metadata["attempt_limit"]: + flag_reject.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag, reason="attempt_limit_reached") + return FormattedResponse(d={"correct": False}, m="attempt_limit_reached") flag_submit.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag) if not challenge.flag_plugin.check(flag, user=user, team=team): - flag_reject.send(sender=self.__class__, user=user, team=team, - challenge=challenge, flag=flag, reason='incorrect_flag') + flag_reject.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag, reason="incorrect_flag") challenge.points_plugin.register_incorrect_attempt(user, team, flag, solve_set) - return FormattedResponse(d={'correct': False}, m='incorrect_flag') + return FormattedResponse(d={"correct": False}, m="incorrect_flag") solve = challenge.points_plugin.score(user, team, flag, solve_set) if challenge.first_blood is None: @@ -268,64 +272,60 @@ def post(self, request): user.save() team.save() flag_score.send(sender=self.__class__, user=user, team=team, challenge=challenge, flag=flag, solve=solve) - caches['default'].delete(get_cache_key(request.user)) - ret = {'correct': True} + caches["default"].delete(get_cache_key(request.user)) + ret = {"correct": True} if challenge.post_score_explanation: ret["explanation"] = challenge.post_score_explanation - return FormattedResponse(d=ret, m='correct_flag') + return FormattedResponse(d=ret, m="correct_flag") class FlagCheckView(APIView): permission_classes = (CompetitionOpen & IsAuthenticated & HasTeam & ~IsBot,) - throttle_scope = 'flag_submit' + throttle_scope = "flag_submit" def post(self, request): - if not config.config.get('enable_flag_submission') or \ - (not config.config.get('enable_flag_submission_after_competition') and time.time() > config.config.get('end_time')): - return FormattedResponse(m='flag_submission_disabled', status=HTTP_403_FORBIDDEN) + if not config.config.get("enable_flag_submission") or (not config.config.get("enable_flag_submission_after_competition") and time.time() > config.config.get("end_time")): + return FormattedResponse(m="flag_submission_disabled", status=HTTP_403_FORBIDDEN) team = Team.objects.get(id=request.user.team.id) user = get_user_model().objects.get(id=request.user.id) - flag = request.data.get('flag') - challenge_id = request.data.get('challenge') + flag = request.data.get("flag") + challenge_id = request.data.get("challenge") if not flag or not challenge_id: return FormattedResponse(status=HTTP_400_BAD_REQUEST) challenge = get_object_or_404(Challenge.objects.select_for_update(), id=challenge_id) solve_set = Solve.objects.filter(challenge=challenge) if not solve_set.filter(team=team, correct=True).exists(): - return FormattedResponse(m='havent_solved_challenge', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="havent_solved_challenge", status=HTTP_403_FORBIDDEN) if not challenge.flag_plugin.check(flag, user=user, team=team): - return FormattedResponse(d={'correct': False}, m='incorrect_flag') + return FormattedResponse(d={"correct": False}, m="incorrect_flag") - ret = {'correct': True} + ret = {"correct": True} if challenge.post_score_explanation: ret["explanation"] = challenge.post_score_explanation - return FormattedResponse(d=ret, m='correct_flag') + return FormattedResponse(d=ret, m="correct_flag") class FileViewSet(ModelViewSet): queryset = File.objects.all() permission_classes = (IsAdminUser,) parser_classes = (MultiPartParser,) - throttle_scope = 'file' + throttle_scope = "file" serializer_class = FileSerializer pagination_class = None def create(self, request: Request, *args, **kwargs) -> Union[FormattedResponse, Response]: """Create a File, given a URL or from a direct upload.""" challenge = get_object_or_404(Challenge, id=request.data.get("challenge")) - file_url, file_data, file_size, file_digest, file_name = ( - request.data.get(name) for name in ("url", "upload", "size", "md5", "name") - ) + file_url, file_data, file_size, file_digest, file_name = (request.data.get(name) for name in ("url", "upload", "size", "md5", "name")) if not file_url and not file_data: return FormattedResponse(m="Either url or upload must be provided.", status=HTTP_400_BAD_REQUEST) if file_data: if len(file_data) > settings.MAX_UPLOAD_SIZE: - return FormattedResponse(m=f"File cannot be over {settings.MAX_UPLOAD_SIZE} bytes in size.", - status=HTTP_400_BAD_REQUEST) + return FormattedResponse(m=f"File cannot be over {settings.MAX_UPLOAD_SIZE} bytes in size.", status=HTTP_400_BAD_REQUEST) file = File(challenge=challenge, upload=file_data) file.name = file.upload.name file.size = file.upload.size @@ -357,6 +357,6 @@ def destroy(self, request: Request, *args, **kwargs) -> Response: class TagViewSet(ModelViewSet): queryset = Tag.objects.all() permission_classes = (IsAdminUser,) - throttle_scope = 'tag' + throttle_scope = "tag" serializer_class = TagSerializer pagination_class = None diff --git a/src/config/config.py b/src/config/config.py index 9c2335f5..a4d917f7 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -2,7 +2,7 @@ from django.conf import settings -backend = locate(settings.CONFIG['BACKEND'])() +backend = locate(settings.CONFIG["BACKEND"])() backend.load(defaults=settings.DEFAULT_CONFIG) @@ -19,7 +19,7 @@ def get_all(): def get_all_non_sensitive(): - sensitive = backend.get('sensitive_fields') + sensitive = backend.get("sensitive_fields") config = backend.get_all() for field in sensitive: del config[field] @@ -27,7 +27,7 @@ def get_all_non_sensitive(): def is_sensitive(key): - return key in backend.get('sensitive_fields') + return key in backend.get("sensitive_fields") def set_bulk(values: dict): diff --git a/src/config/tests.py b/src/config/tests.py index 3e1eb663..bb938482 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -7,46 +7,42 @@ class ConfigTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='config-test', email='config-test@example.org') + user = get_user_model()(username="config-test", email="config-test@example.org") user.is_staff = True user.save() self.staff_user = user - user2 = get_user_model()(username='config-test2', email='config-test2@example.org') + user2 = get_user_model()(username="config-test2", email="config-test2@example.org") user2.save() self.user = user2 def test_auth_unauthed(self): - response = self.client.get(reverse('config-list')) + response = self.client.get(reverse("config-list")) self.assertEqual(response.status_code, HTTP_200_OK) def test_auth_authed(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('config-list')) + response = self.client.get(reverse("config-list")) self.assertEqual(response.status_code, HTTP_200_OK) def test_auth_authed_staff(self): self.client.force_authenticate(self.staff_user) - response = self.client.get(reverse('config-list')) + response = self.client.get(reverse("config-list")) self.assertEqual(response.status_code, HTTP_200_OK) def test_post_authed(self): self.client.force_authenticate(self.user) - response = self.client.post(reverse('config-pk', kwargs={'name': 'test'}), - data={'key': 'test', 'value': 'test'}, format='json') + response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"key": "test", "value": "test"}, format="json") self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create(self): self.client.force_authenticate(self.staff_user) - response = self.client.post(reverse('config-pk', kwargs={'name': 'test'}), - data={'value': 'test'}, format='json') + response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") self.assertEqual(response.status_code, HTTP_201_CREATED) def test_update(self): self.client.force_authenticate(self.staff_user) - self.client.post(reverse('config-pk', kwargs={'name': 'test'}), data={'value': 'test'}, format='json') - response = self.client.patch(reverse('config-pk', kwargs={'name': 'test'}), - data={'value': 'test2'}, format='json') + self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") + response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) - self.assertEqual(config.config.get('test'), 'test2') + self.assertEqual(config.config.get("test"), "test2") diff --git a/src/config/urls.py b/src/config/urls.py index 78adae69..1db06ebe 100644 --- a/src/config/urls.py +++ b/src/config/urls.py @@ -2,7 +2,4 @@ from config import views -urlpatterns = [ - path('', views.ConfigView.as_view(), name='config-list'), - path('/', views.ConfigView.as_view(), name='config-pk') -] +urlpatterns = [path("", views.ConfigView.as_view(), name="config-list"), path("/", views.ConfigView.as_view(), name="config-pk")] diff --git a/src/experiments/apps.py b/src/experiments/apps.py index 2eb5d051..864b3dee 100644 --- a/src/experiments/apps.py +++ b/src/experiments/apps.py @@ -2,4 +2,4 @@ class ExperimentsConfig(AppConfig): - name = 'experiments' + name = "experiments" diff --git a/src/experiments/models.py b/src/experiments/models.py index eadab150..061d2a59 100644 --- a/src/experiments/models.py +++ b/src/experiments/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/experiments/urls.py b/src/experiments/urls.py index f2e516d3..6064cca1 100644 --- a/src/experiments/urls.py +++ b/src/experiments/urls.py @@ -3,5 +3,5 @@ from experiments import views urlpatterns = [ - path('', views.ExperimentView.as_view(), name='experiments'), + path("", views.ExperimentView.as_view(), name="experiments"), ] diff --git a/src/gunicorn_config.py b/src/gunicorn_config.py index 5c53ac35..aa3e7ae0 100644 --- a/src/gunicorn_config.py +++ b/src/gunicorn_config.py @@ -1,4 +1,5 @@ from prometheus_client import multiprocess + def child_exit(server, worker): multiprocess.mark_process_dead(worker.pid) diff --git a/src/hint/models.py b/src/hint/models.py index c3dcdbc1..50007a62 100644 --- a/src/hint/models.py +++ b/src/hint/models.py @@ -19,9 +19,7 @@ class Hint(ExportModelOperationsMixin("hint"), models.Model): class HintUse(ExportModelOperationsMixin("hint_use"), models.Model): hint = models.ForeignKey(Hint, related_name="uses", on_delete=CASCADE) team = models.ForeignKey(Team, related_name="hints_used", on_delete=CASCADE, null=True) - user = models.ForeignKey( - get_user_model(), related_name="hints_used", on_delete=SET_NULL, null=True - ) + user = models.ForeignKey(get_user_model(), related_name="hints_used", on_delete=SET_NULL, null=True) timestamp = models.DateTimeField(default=timezone.now) challenge = models.ForeignKey(Challenge, related_name="hints_used", on_delete=CASCADE) diff --git a/src/hint/permissions.py b/src/hint/permissions.py index 77c66cfb..95ce9ddf 100644 --- a/src/hint/permissions.py +++ b/src/hint/permissions.py @@ -5,12 +5,7 @@ class HasUsedHint(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.user.is_staff and not request.user.should_deny_admin(): return True - return ( - request.method in permissions.SAFE_METHODS - and request.user.team.hints_used.filter(hint=obj).exists() - ) + return request.method in permissions.SAFE_METHODS and request.user.team.hints_used.filter(hint=obj).exists() def has_permission(self, request, view): - return request.method in permissions.SAFE_METHODS or ( - request.user.is_staff and not request.user.should_deny_admin() - ) + return request.method in permissions.SAFE_METHODS or (request.user.is_staff and not request.user.should_deny_admin()) diff --git a/src/hint/serializers.py b/src/hint/serializers.py index c698b7e2..318f1ccb 100644 --- a/src/hint/serializers.py +++ b/src/hint/serializers.py @@ -5,10 +5,7 @@ def is_used(context, instance): - return ( - context["request"].user.team is not None - and context["request"].user.team.hints_used.filter(hint=instance).exists() - ) + return context["request"].user.team is not None and context["request"].user.team.hints_used.filter(hint=instance).exists() class HintUseSerializer(serializers.ModelSerializer): @@ -19,10 +16,7 @@ class Meta: class HintSerializerMixin: def get_text(self, instance): - if ( - self.context["request"].user.is_staff - and not self.context["request"].user.should_deny_admin() - ) or is_used(self.context, instance): + if (self.context["request"].user.is_staff and not self.context["request"].user.should_deny_admin()) or is_used(self.context, instance): return instance.text else: return "" diff --git a/src/hint/urls.py b/src/hint/urls.py index 8812b73d..1946fbe4 100644 --- a/src/hint/urls.py +++ b/src/hint/urls.py @@ -4,9 +4,9 @@ from hint import views router = DefaultRouter() -router.register(r'', views.HintViewSet, basename='hint') +router.register(r"", views.HintViewSet, basename="hint") urlpatterns = [ - path('use/', views.UseHintView.as_view(), name='hint-use'), - path('', include(router.urls)), + path("use/", views.UseHintView.as_view(), name="hint-use"), + path("", include(router.urls)), ] diff --git a/src/hint/views.py b/src/hint/views.py index 180d47de..b704ffa3 100644 --- a/src/hint/views.py +++ b/src/hint/views.py @@ -41,16 +41,10 @@ def post(self, request): hint_id = serializer.validated_data["id"] hint = get_object_or_404(Hint, id=hint_id) if not hint.challenge.is_unlocked(request.user): - return FormattedResponse( - m="challenge_not_unlocked", s=False, status=HTTP_403_FORBIDDEN - ) + return FormattedResponse(m="challenge_not_unlocked", s=False, status=HTTP_403_FORBIDDEN) if HintUse.objects.filter(hint=hint, team=request.user.team).exists(): - return FormattedResponse( - m="hint_already_used", s=False, status=HTTP_403_FORBIDDEN - ) - use_hint.send( - sender=self.__class__, user=request.user, team=request.user.team, hint=hint - ) + return FormattedResponse(m="hint_already_used", s=False, status=HTTP_403_FORBIDDEN) + use_hint.send(sender=self.__class__, user=request.user, team=request.user.team, hint=hint) HintUse( hint=hint, team=request.user.team, @@ -58,5 +52,5 @@ def post(self, request): challenge=hint.challenge, ).save() serializer = FullHintSerializer(hint, context={"request": request}) - caches['default'].delete(get_cache_key(request.user)) + caches["default"].delete(get_cache_key(request.user)) return FormattedResponse(d=serializer.data) diff --git a/src/leaderboard/serializers.py b/src/leaderboard/serializers.py index 884abbe0..49579b8c 100644 --- a/src/leaderboard/serializers.py +++ b/src/leaderboard/serializers.py @@ -43,7 +43,7 @@ class MatrixSerializer(serializers.ModelSerializer): class Meta: model = Team - fields = ['id', 'name', 'leaderboard_points', 'solve_ids'] + fields = ["id", "name", "leaderboard_points", "solve_ids"] def get_solve_ids(self, instance): - return list(instance.solves.values_list('challenge', flat=True)) + return list(instance.solves.values_list("challenge", flat=True)) diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 3b3ee332..185190c5 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -10,16 +10,25 @@ def populate(): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge = Challenge(name='test3', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='aaa', score=1000, unlock_requirements="") + challenge = Challenge( + name="test3", + category=category, + description="a", + challenge_type="basic", + challenge_metadata={}, + flag_type="plaintext", + flag_metadata={"flag": "ractf{a}"}, + author="aaa", + score=1000, + unlock_requirements="", + ) challenge.save() for i in range(15): - user = get_user_model()(username=f'scorelist-test{i}', email=f'scorelist-test{i}@example.org', is_visible=True) + user = get_user_model()(username=f"scorelist-test{i}", email=f"scorelist-test{i}@example.org", is_visible=True) user.save() - team = Team(name=f'scorelist-test{i}', password=f'scorelist-test{i}', owner=user, is_visible=True) + team = Team(name=f"scorelist-test{i}", password=f"scorelist-test{i}", owner=user, is_visible=True) team.points = i * 100 team.leaderboard_points = i * 100 team.save() @@ -27,190 +36,185 @@ def populate(): user.points = i * 100 user.leaderboard_points = i * 100 user.save() - Score(team=team, user=user, reason='test', points=i * 100).save() + Score(team=team, user=user, reason="test", points=i * 100).save() if i % 2 == 0: Solve(team=team, solved_by=user, challenge=challenge).save() class ScoreListTestCase(APITestCase): - def setUp(self): - GraphView.throttle_scope = '' - user = get_user_model()(username='scorelist-test', email='scorelist-test@example.org') + GraphView.throttle_scope = "" + user = get_user_model()(username="scorelist-test", email="scorelist-test@example.org") user.save() self.user = user def test_unauthed_access(self): - config.config.set('enable_caching', False) - response = self.client.get(reverse('leaderboard-graph')) - config.config.set('enable_caching', True) + config.config.set("enable_caching", False) + response = self.client.get(reverse("leaderboard-graph")) + config.config.set("enable_caching", True) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_access(self): self.client.force_authenticate(self.user) - config.config.set('enable_caching', False) - response = self.client.get(reverse('leaderboard-graph')) - config.config.set('enable_caching', True) + config.config.set("enable_caching", False) + response = self.client.get(reverse("leaderboard-graph")) + config.config.set("enable_caching", True) self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.config.set('enable_caching', False) + config.config.set("enable_caching", False) config.config.set("enable_scoreboard", False) - response = self.client.get(reverse('leaderboard-graph')) + response = self.client.get(reverse("leaderboard-graph")) config.config.set("enable_scoreboard", True) - config.config.set('enable_caching', True) + config.config.set("enable_caching", True) self.assertEqual(response.data["d"], {}) def test_format(self): - config.config.set('enable_caching', False) - response = self.client.get(reverse('leaderboard-graph')) - config.config.set('enable_caching', True) - self.assertTrue('user' in response.data['d']) - self.assertTrue('team' in response.data['d']) + config.config.set("enable_caching", False) + response = self.client.get(reverse("leaderboard-graph")) + config.config.set("enable_caching", True) + self.assertTrue("user" in response.data["d"]) + self.assertTrue("team" in response.data["d"]) def test_list_size(self): - config.config.set('enable_caching', False) + config.config.set("enable_caching", False) populate() - response = self.client.get(reverse('leaderboard-graph')) - config.config.set('enable_caching', True) - self.assertEqual(len(response.data['d']['user']), 10) - self.assertEqual(len(response.data['d']['team']), 10) + response = self.client.get(reverse("leaderboard-graph")) + config.config.set("enable_caching", True) + self.assertEqual(len(response.data["d"]["user"]), 10) + self.assertEqual(len(response.data["d"]["team"]), 10) def test_list_sorting(self): - config.config.set('enable_caching', False) + config.config.set("enable_caching", False) populate() - response = self.client.get(reverse('leaderboard-graph')) - config.config.set('enable_caching', True) - self.assertEqual(response.data['d']['user'][0]['points'], 1400) - self.assertEqual(response.data['d']['team'][0]['points'], 1400) + response = self.client.get(reverse("leaderboard-graph")) + config.config.set("enable_caching", True) + self.assertEqual(response.data["d"]["user"][0]["points"], 1400) + self.assertEqual(response.data["d"]["team"][0]["points"], 1400) def test_user_only(self): populate() config.config.set("enable_teams", False) - config.config.set('enable_caching', False) - response = self.client.get(reverse('leaderboard-graph')) + config.config.set("enable_caching", False) + response = self.client.get(reverse("leaderboard-graph")) config.config.set("enable_teams", True) - config.config.set('enable_caching', True) - self.assertEqual(len(response.data['d']['user']), 10) - self.assertEqual(response.data['d']['user'][0]['points'], 1400) - self.assertNotIn("team", response.data['d'].keys()) + config.config.set("enable_caching", True) + self.assertEqual(len(response.data["d"]["user"]), 10) + self.assertEqual(response.data["d"]["user"][0]["points"], 1400) + self.assertNotIn("team", response.data["d"].keys()) class UserListTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='userlist-test', email='userlist-test@example.org') + user = get_user_model()(username="userlist-test", email="userlist-test@example.org") user.save() self.user = user UserListView.throttle_scope = None def test_unauthed(self): - response = self.client.get(reverse('leaderboard-user')) + response = self.client.get(reverse("leaderboard-user")) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-user')) + response = self.client.get(reverse("leaderboard-user")) self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) - response = self.client.get(reverse('leaderboard-user')) + response = self.client.get(reverse("leaderboard-user")) config.config.set("enable_scoreboard", True) self.assertEqual(response.data["d"], {}) def test_length(self): populate() - response = self.client.get(reverse('leaderboard-user')) - self.assertEqual(len(response.data['d']['results']), 15) + response = self.client.get(reverse("leaderboard-user")) + self.assertEqual(len(response.data["d"]["results"]), 15) def test_order(self): populate() - response = self.client.get(reverse('leaderboard-user')) - points = [x['leaderboard_points'] for x in response.data['d']['results']] + response = self.client.get(reverse("leaderboard-user")) + points = [x["leaderboard_points"] for x in response.data["d"]["results"]] self.assertEqual(points, sorted(points, reverse=True)) class TeamListTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='userlist-test', email='userlist-test@example.org') + user = get_user_model()(username="userlist-test", email="userlist-test@example.org") user.save() self.user = user TeamListView.throttle_scope = None def test_unauthed(self): - response = self.client.get(reverse('leaderboard-team')) + response = self.client.get(reverse("leaderboard-team")) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-team')) + response = self.client.get(reverse("leaderboard-team")) self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) - response = self.client.get(reverse('leaderboard-team')) + response = self.client.get(reverse("leaderboard-team")) config.config.set("enable_scoreboard", True) self.assertEqual(response.data["d"], {}) def test_length(self): populate() - response = self.client.get(reverse('leaderboard-team')) + response = self.client.get(reverse("leaderboard-team")) self.assertEqual(len(response.data["d"]["results"]), 15) def test_order(self): populate() - response = self.client.get(reverse('leaderboard-team')) - points = [x['leaderboard_points'] for x in response.data['d']['results']] + response = self.client.get(reverse("leaderboard-team")) + points = [x["leaderboard_points"] for x in response.data["d"]["results"]] self.assertEqual(points, sorted(points, reverse=True)) class CTFTimeListTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='userlist-test', email='userlist-test@example.org') + user = get_user_model()(username="userlist-test", email="userlist-test@example.org") user.save() self.user = user CTFTimeListView.throttle_scope = None def test_unauthed(self): - response = self.client.get(reverse('leaderboard-ctftime')) + response = self.client.get(reverse("leaderboard-ctftime")) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-ctftime')) + response = self.client.get(reverse("leaderboard-ctftime")) self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): config.config.set("enable_scoreboard", False) - response = self.client.get(reverse('leaderboard-ctftime')) + response = self.client.get(reverse("leaderboard-ctftime")) config.config.set("enable_scoreboard", True) self.assertEqual(response.data, {}) def test_disabled_ctftime(self): config.config.set("enable_ctftime", False) - response = self.client.get(reverse('leaderboard-ctftime')) + response = self.client.get(reverse("leaderboard-ctftime")) config.config.set("enable_ctftime", True) self.assertEqual(response.data, {}) def test_length(self): populate() - response = self.client.get(reverse('leaderboard-ctftime')) + response = self.client.get(reverse("leaderboard-ctftime")) self.assertEqual(len(response.data["standings"]), 15) def test_order(self): populate() - response = self.client.get(reverse('leaderboard-ctftime')) - points = [x['score'] for x in response.data['standings']] + response = self.client.get(reverse("leaderboard-ctftime")) + points = [x["score"] for x in response.data["standings"]] self.assertEqual(points, sorted(points, reverse=True)) class MatrixTestCase(APITestCase): - def setUp(self): - user = get_user_model()(username='matrix-test', email='matrix-test@example.org') + user = get_user_model()(username="matrix-test", email="matrix-test@example.org") user.save() self.user = user TeamListView.throttle_scope = None @@ -218,36 +222,36 @@ def setUp(self): def test_authenticated(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-matrix-list')) + response = self.client.get(reverse("leaderboard-matrix-list")) self.assertEqual(response.status_code, HTTP_200_OK) def test_unauthenticated(self): - response = self.client.get(reverse('leaderboard-matrix-list')) + response = self.client.get(reverse("leaderboard-matrix-list")) self.assertEqual(response.status_code, HTTP_200_OK) def test_length(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEqual(len(response.data['d']['results']), 15) + response = self.client.get(reverse("leaderboard-matrix-list")) + self.assertEqual(len(response.data["d"]["results"]), 15) def test_solves_present(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEqual(len(response.data['d']['results'][0]['solve_ids']), 1) + response = self.client.get(reverse("leaderboard-matrix-list")) + self.assertEqual(len(response.data["d"]["results"][0]["solve_ids"]), 1) def test_solves_not_present(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-matrix-list')) - self.assertEqual(len(response.data['d']['results'][1]['solve_ids']), 0) + response = self.client.get(reverse("leaderboard-matrix-list")) + self.assertEqual(len(response.data["d"]["results"][1]["solve_ids"]), 0) def test_order(self): self.client.force_authenticate(self.user) - response = self.client.get(reverse('leaderboard-matrix-list')) - points = [x['leaderboard_points'] for x in response.data['d']['results']] + response = self.client.get(reverse("leaderboard-matrix-list")) + points = [x["leaderboard_points"] for x in response.data["d"]["results"]] self.assertEqual(points, sorted(points, reverse=True)) def test_disabled_scoreboard(self): config.config.set("enable_scoreboard", False) - response = self.client.get(reverse('leaderboard-matrix-list')) + response = self.client.get(reverse("leaderboard-matrix-list")) config.config.set("enable_scoreboard", True) - self.assertEqual(response.data['d'], {}) + self.assertEqual(response.data["d"], {}) diff --git a/src/leaderboard/urls.py b/src/leaderboard/urls.py index c1db7bce..475bb570 100644 --- a/src/leaderboard/urls.py +++ b/src/leaderboard/urls.py @@ -4,12 +4,12 @@ from leaderboard import views router = DefaultRouter() -router.register('matrix/', views.MatrixScoreboardView, basename='leaderboard-matrix') +router.register("matrix/", views.MatrixScoreboardView, basename="leaderboard-matrix") urlpatterns = [ - path('ctftime/', views.CTFTimeListView.as_view(), name='leaderboard-ctftime'), - path('graph/', views.GraphView.as_view(), name='leaderboard-graph'), - path('user/', views.UserListView.as_view(), name='leaderboard-user'), - path('team/', views.TeamListView.as_view(), name='leaderboard-team'), - path('', include(router.urls)), + path("ctftime/", views.CTFTimeListView.as_view(), name="leaderboard-ctftime"), + path("graph/", views.GraphView.as_view(), name="leaderboard-graph"), + path("user/", views.UserListView.as_view(), name="leaderboard-user"), + path("team/", views.TeamListView.as_view(), name="leaderboard-team"), + path("", include(router.urls)), ] diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index 3a9e7580..d42aba09 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -11,62 +11,61 @@ from backend.response import FormattedResponse from challenge.models import Score import config -from leaderboard.serializers import LeaderboardUserScoreSerializer, LeaderboardTeamScoreSerializer, \ - UserPointsSerializer, TeamPointsSerializer, CTFTimeSerializer, MatrixSerializer +from leaderboard.serializers import LeaderboardUserScoreSerializer, LeaderboardTeamScoreSerializer, UserPointsSerializer, TeamPointsSerializer, CTFTimeSerializer, MatrixSerializer from team.models import Team def should_hide_scoreboard(): - return not config.config.get('enable_scoreboard') and (config.config.get('hide_scoreboard_at') == -1 or - config.config.get('hide_scoreboard_at') > time.time() or - config.config.get('end_time') > time.time()) + return not config.config.get("enable_scoreboard") and ( + config.config.get("hide_scoreboard_at") == -1 or config.config.get("hide_scoreboard_at") > time.time() or config.config.get("end_time") > time.time() + ) class CTFTimeListView(APIView): - renderer_classes = (JSONRenderer, BrowsableAPIRenderer,) + renderer_classes = ( + JSONRenderer, + BrowsableAPIRenderer, + ) def get(self, request, *args, **kwargs): - if should_hide_scoreboard() or not config.config.get('enable_ctftime'): + if should_hide_scoreboard() or not config.config.get("enable_ctftime"): return Response({}) teams = Team.objects.visible().ranked() return Response({"standings": CTFTimeSerializer(teams, many=True).data}) class GraphView(APIView): - throttle_scope = 'leaderboard' + throttle_scope = "leaderboard" def get(self, request, *args, **kwargs): if should_hide_scoreboard(): return FormattedResponse({}) - cache = caches['default'] - cached_leaderboard = cache.get('leaderboard_graph') - if cached_leaderboard is not None and config.config.get('enable_caching'): + cache = caches["default"] + cached_leaderboard = cache.get("leaderboard_graph") + if cached_leaderboard is not None and config.config.get("enable_caching"): return FormattedResponse(cached_leaderboard) - graph_members = config.config.get('graph_members') + graph_members = config.config.get("graph_members") top_teams = Team.objects.visible().ranked()[:graph_members] - top_users = get_user_model().objects.filter(is_visible=True).order_by('-leaderboard_points', 'last_score')[ - :graph_members] + top_users = get_user_model().objects.filter(is_visible=True).order_by("-leaderboard_points", "last_score")[:graph_members] - team_scores = Score.objects.filter(team__in=top_teams, leaderboard=True).select_related('team') \ - .order_by('-team__leaderboard_points', 'team__last_score') - user_scores = Score.objects.filter(user__in=top_users, leaderboard=True).select_related('user') \ - .order_by('-user__leaderboard_points', 'user__last_score') + team_scores = Score.objects.filter(team__in=top_teams, leaderboard=True).select_related("team").order_by("-team__leaderboard_points", "team__last_score") + user_scores = Score.objects.filter(user__in=top_users, leaderboard=True).select_related("user").order_by("-user__leaderboard_points", "user__last_score") user_serializer = LeaderboardUserScoreSerializer(user_scores, many=True) team_serializer = LeaderboardTeamScoreSerializer(team_scores, many=True) - response = {'user': user_serializer.data} - if config.config.get('enable_teams'): - response['team'] = team_serializer.data + response = {"user": user_serializer.data} + if config.config.get("enable_teams"): + response["team"] = team_serializer.data - cache.set('leaderboard_graph', response, 15) + cache.set("leaderboard_graph", response, 15) return FormattedResponse(response) class UserListView(ListAPIView): - throttle_scope = 'leaderboard' - queryset = get_user_model().objects.filter(is_visible=True).order_by('-leaderboard_points', 'last_score') + throttle_scope = "leaderboard" + queryset = get_user_model().objects.filter(is_visible=True).order_by("-leaderboard_points", "last_score") serializer_class = UserPointsSerializer def list(self, request, *args, **kwargs): @@ -76,7 +75,7 @@ def list(self, request, *args, **kwargs): class TeamListView(ListAPIView): - throttle_scope = 'leaderboard' + throttle_scope = "leaderboard" queryset = Team.objects.visible().ranked() serializer_class = TeamPointsSerializer @@ -87,7 +86,7 @@ def list(self, request, *args, **kwargs): class MatrixScoreboardView(ReadOnlyModelViewSet): - throttle_scope = 'leaderboard' + throttle_scope = "leaderboard" queryset = Team.objects.visible().ranked().prefetch_solves() serializer_class = MatrixSerializer diff --git a/src/manage.py b/src/manage.py index 87d21c53..a83a7e6e 100755 --- a/src/manage.py +++ b/src/manage.py @@ -9,11 +9,7 @@ def main(): try: from django.core.management import execute_from_command_line except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc + raise ImportError("Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?") from exc execute_from_command_line(sys.argv) diff --git a/src/member/models.py b/src/member/models.py index 96ae650e..490b67d2 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -56,10 +56,8 @@ def __str__(self): def can_login(self): from config import config - return self.is_staff or ( - config.get("enable_login") - and (config.get("enable_prelogin") or config.get("start_time") <= time.time()) - ) + + return self.is_staff or (config.get("enable_login") and (config.get("enable_prelogin") or config.get("start_time") <= time.time())) def issue_token(self, owner=None): from authentication.models import Token @@ -73,6 +71,7 @@ def has_2fa(self): def should_deny_admin(self): from config import config + return config.get("enable_force_admin_2fa") and not self.has_2fa() diff --git a/src/member/serializers.py b/src/member/serializers.py index 4a67a4cb..47444cc2 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -11,62 +11,133 @@ class MemberSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): solves = SolveSerializer(many=True, read_only=True) - team_name = serializers.ReadOnlyField(source='team.name') + team_name = serializers.ReadOnlyField(source="team.name") incorrect_solves = serializers.SerializerMethodField() class Meta: model = get_user_model() - fields = ['id', 'username', 'is_staff', 'bio', 'discord', 'discordid', 'state_actor', 'twitter', 'reddit', - 'team', 'points', 'is_visible', 'is_active', 'solves', 'team_name', 'leaderboard_points', - 'date_joined', 'incorrect_solves', 'is_verified'] + fields = [ + "id", + "username", + "is_staff", + "bio", + "discord", + "discordid", + "state_actor", + "twitter", + "reddit", + "team", + "points", + "is_visible", + "is_active", + "solves", + "team_name", + "leaderboard_points", + "date_joined", + "incorrect_solves", + "is_verified", + ] class ListMemberSerializer(serializers.ModelSerializer): - team_name = serializers.ReadOnlyField(source='team.name') + team_name = serializers.ReadOnlyField(source="team.name") class Meta: model = get_user_model() - fields = ['id', 'username', 'team', 'team_name'] + fields = ["id", "username", "team", "team_name"] class AdminMemberSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): solves = SolveSerializer(many=True, read_only=True) - team_name = serializers.ReadOnlyField(source='team.name') + team_name = serializers.ReadOnlyField(source="team.name") incorrect_solves = serializers.SerializerMethodField() class Meta: model = get_user_model() - fields = ['id', 'username', 'is_staff', 'bio', 'discord', 'discordid', 'twitter', 'reddit', 'team', - 'points', 'is_visible', 'is_active', 'solves', 'team_name', 'email', 'email_verified', - 'leaderboard_points', 'date_joined', 'state_actor', 'incorrect_solves', 'is_verified'] + fields = [ + "id", + "username", + "is_staff", + "bio", + "discord", + "discordid", + "twitter", + "reddit", + "team", + "points", + "is_visible", + "is_active", + "solves", + "team_name", + "email", + "email_verified", + "leaderboard_points", + "date_joined", + "state_actor", + "incorrect_solves", + "is_verified", + ] class MinimalMemberSerializer(serializers.ModelSerializer): - team_name = serializers.ReadOnlyField(source='team.name') + team_name = serializers.ReadOnlyField(source="team.name") class Meta: model = get_user_model() - fields = ['id', 'username', 'is_staff', 'bio', 'discord', 'discordid', 'twitter', 'reddit', 'team', - 'points', 'is_visible', 'is_active', 'team_name', 'leaderboard_points', 'state_actor', 'date_joined', - 'is_verified'] + fields = [ + "id", + "username", + "is_staff", + "bio", + "discord", + "discordid", + "twitter", + "reddit", + "team", + "points", + "is_visible", + "is_active", + "team_name", + "leaderboard_points", + "state_actor", + "date_joined", + "is_verified", + ] class SelfSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): from team.serializers import MinimalTeamSerializer + solves = SolveSerializer(many=True, read_only=True) team = MinimalTeamSerializer(read_only=True) - team_name = serializers.ReadOnlyField(source='team.name') + team_name = serializers.ReadOnlyField(source="team.name") email = serializers.EmailField() incorrect_solves = serializers.SerializerMethodField() has_2fa = serializers.BooleanField() class Meta: model = get_user_model() - fields = ['id', 'username', 'is_staff', 'bio', 'discord', 'discordid', 'twitter', 'reddit', 'team', 'email', - 'has_2fa', 'points', 'solves', 'team_name', 'leaderboard_points', 'date_joined', - 'incorrect_solves', 'is_verified'] - read_only_fields = ['id', 'is_staff', 'team', 'points', 'leaderboard_points', 'date_joined', - 'incorrect_solves', 'is_verified'] + fields = [ + "id", + "username", + "is_staff", + "bio", + "discord", + "discordid", + "twitter", + "reddit", + "team", + "email", + "has_2fa", + "points", + "solves", + "team_name", + "leaderboard_points", + "date_joined", + "incorrect_solves", + "is_verified", + ] + read_only_fields = ["id", "is_staff", "team", "points", "leaderboard_points", "date_joined", "incorrect_solves", "is_verified"] def validate_email(self, value): self.instance.password_reset_token = secrets.token_hex() @@ -84,4 +155,4 @@ def update(self, instance, validated_data): class UserIPSerializer(serializers.ModelSerializer): class Meta: model = UserIP - fields = ['user', 'ip', 'seen', 'last_seen', 'user_agent'] \ No newline at end of file + fields = ["user", "ip", "seen", "last_seen", "user_agent"] diff --git a/src/member/tests.py b/src/member/tests.py index 5be9b998..ee69594a 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -30,9 +30,7 @@ def test_self_status_unauth(self): def test_self_change_email(self): self.client.force_authenticate(self.user) - response = self.client.put( - reverse("member-self"), data={"email": "test-self2@example.org", "username": "test-self"} - ) + response = self.client.put(reverse("member-self"), data={"email": "test-self2@example.org", "username": "test-self"}) self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(get_user_model().objects.get(id=self.user.id).email, "test-self2@example.org") @@ -45,9 +43,7 @@ def test_self_change_email_token_change(self): pr_token = self.user.password_reset_token ev_token = self.user.email_token self.client.force_authenticate(self.user) - response = self.client.put( - reverse("member-self"), data={"email": "test-self3@example.org"} - ) + response = self.client.put(reverse("member-self"), data={"email": "test-self3@example.org"}) user = get_user_model().objects.get(id=self.user.id) self.assertNotEqual(pr_token, user.password_reset_token) self.assertNotEqual(ev_token, user.email_token) @@ -69,9 +65,7 @@ def setUp(self): self.admin_user = user def test_visible_admin(self): - user = get_user_model()( - username="test-member-invisible", email="test-member-invisible@example.org" - ) + user = get_user_model()(username="test-member-invisible", email="test-member-invisible@example.org") user.is_visible = False user.save() self.client.force_authenticate(self.admin_user) @@ -79,9 +73,7 @@ def test_visible_admin(self): self.assertEqual(len(response.data["d"]["results"]), 3) def test_visible_not_admin(self): - user = get_user_model()( - username="test-member-invisible", email="test-member-invisible@example.org" - ) + user = get_user_model()(username="test-member-invisible", email="test-member-invisible@example.org") user.is_visible = False user.save() self.client.force_authenticate(self.user) @@ -89,9 +81,7 @@ def test_visible_not_admin(self): self.assertEqual(len(response.data["d"]["results"]), 0) def test_visible_detail_admin(self): - user = get_user_model()( - username="test-member-invisible", email="test-member-invisible@example.org" - ) + user = get_user_model()(username="test-member-invisible", email="test-member-invisible@example.org") user.is_visible = False user.save() self.client.force_authenticate(self.admin_user) @@ -99,9 +89,7 @@ def test_visible_detail_admin(self): self.assertEqual(response.status_code, HTTP_200_OK) def test_visible_detail_not_admin(self): - user = get_user_model()( - username="test-member-invisible", email="test-member-invisible@example.org" - ) + user = get_user_model()(username="test-member-invisible", email="test-member-invisible@example.org") user.is_visible = False user.save() self.client.force_authenticate(self.user) @@ -110,25 +98,19 @@ def test_visible_detail_not_admin(self): def test_view_email_admin(self): self.client.force_authenticate(self.admin_user) - response = self.client.get( - reverse("member-detail", kwargs={"pk": self.user.id}) - ) + response = self.client.get(reverse("member-detail", kwargs={"pk": self.user.id})) self.assertTrue("email" in response.data) def test_view_email_not_admin(self): self.client.force_authenticate(self.user) - response = self.client.get( - reverse("member-detail", kwargs={"pk": self.admin_user.id}) - ) + response = self.client.get(reverse("member-detail", kwargs={"pk": self.admin_user.id})) self.assertFalse("email" in response.data) def test_view_member(self): self.admin_user.is_visible = True self.admin_user.save() self.client.force_authenticate(self.user) - response = self.client.get( - reverse("member-detail", kwargs={"pk": self.admin_user.id}) - ) + response = self.client.get(reverse("member-detail", kwargs={"pk": self.admin_user.id})) self.assertEqual(response.status_code, HTTP_200_OK) def test_patch_member(self): diff --git a/src/member/urls.py b/src/member/urls.py index bd42df03..7c55c287 100644 --- a/src/member/urls.py +++ b/src/member/urls.py @@ -4,10 +4,7 @@ from member import views router = DefaultRouter() -router.register(r'', views.MemberViewSet, basename='member') -router.register('', views.UserIPViewSet, basename='userip') +router.register(r"", views.MemberViewSet, basename="member") +router.register("", views.UserIPViewSet, basename="userip") -urlpatterns = [ - path('self/', views.SelfView.as_view(), name='member-self'), - path('', include(router.urls), name='member') -] +urlpatterns = [path("self/", views.SelfView.as_view(), name="member-self"), path("", include(router.urls), name="member")] diff --git a/src/member/views.py b/src/member/views.py index b5d86c7f..ad5a59a1 100644 --- a/src/member/views.py +++ b/src/member/views.py @@ -7,49 +7,70 @@ from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.viewsets import AdminListModelViewSet from member.models import UserIP -from member.serializers import SelfSerializer, MemberSerializer, AdminMemberSerializer, ListMemberSerializer, \ - UserIPSerializer +from member.serializers import SelfSerializer, MemberSerializer, AdminMemberSerializer, ListMemberSerializer, UserIPSerializer class SelfView(RetrieveUpdateAPIView): serializer_class = SelfSerializer permission_classes = (IsAuthenticated & ReadOnlyBot,) - throttle_scope = 'self' + throttle_scope = "self" def get_object(self): UserIP.hook(self.request) - return get_user_model().objects.prefetch_related('team', 'team__solves', 'team__solves__score', - 'team__hints_used', 'team__solves__challenge', - 'team__solves__solved_by', 'solves', - 'solves__score', 'hints_used', 'solves__challenge', - 'solves__team', 'solves__score__team').distinct()\ + return ( + get_user_model() + .objects.prefetch_related( + "team", + "team__solves", + "team__solves__score", + "team__hints_used", + "team__solves__challenge", + "team__solves__solved_by", + "solves", + "solves__score", + "hints_used", + "solves__challenge", + "solves__team", + "solves__score__team", + ) + .distinct() .get(id=self.request.user.id) + ) class MemberViewSet(AdminListModelViewSet): permission_classes = (AdminOrReadOnlyVisible,) - throttle_scope = 'member' + throttle_scope = "member" serializer_class = MemberSerializer admin_serializer_class = AdminMemberSerializer list_serializer_class = ListMemberSerializer list_admin_serializer_class = ListMemberSerializer - search_fields = ['username', 'email'] + search_fields = ["username", "email"] filter_backends = [filters.SearchFilter] def get_queryset(self): - if self.action != 'list': - return get_user_model().objects.prefetch_related('team', 'team__solves', 'team__solves__score', - 'team__hints_used', 'team__solves__challenge', - 'team__solves__solved_by', 'solves', - 'solves__score', 'hints_used', 'solves__challenge', - 'solves__team', 'solves__score__team') + if self.action != "list": + return get_user_model().objects.prefetch_related( + "team", + "team__solves", + "team__solves__score", + "team__hints_used", + "team__solves__challenge", + "team__solves__solved_by", + "solves", + "solves__score", + "hints_used", + "solves__challenge", + "solves__team", + "solves__score__team", + ) if self.request.user.is_staff and not self.request.user.should_deny_admin(): - return get_user_model().objects.order_by('id').prefetch_related('team') - return get_user_model().objects.filter(is_visible=True).order_by('id').prefetch_related('team') + return get_user_model().objects.order_by("id").prefetch_related("team") + return get_user_model().objects.filter(is_visible=True).order_by("id").prefetch_related("team") class UserIPViewSet(ModelViewSet): queryset = UserIP.objects.all() pagination_class = None permission_classes = (IsAdminUser,) - serializer_class = UserIPSerializer \ No newline at end of file + serializer_class = UserIPSerializer diff --git a/src/pages/apps.py b/src/pages/apps.py index acdb9607..344e0f0c 100644 --- a/src/pages/apps.py +++ b/src/pages/apps.py @@ -2,4 +2,4 @@ class PagesConfig(AppConfig): - name = 'pages' + name = "pages" diff --git a/src/pages/serializers.py b/src/pages/serializers.py index d1f3af16..8aae7fc6 100644 --- a/src/pages/serializers.py +++ b/src/pages/serializers.py @@ -5,4 +5,4 @@ class PageSerializer(serializers.ModelSerializer): class Meta: model = Page - fields = ['id', 'url', 'title', 'content'] \ No newline at end of file + fields = ["id", "url", "title", "content"] diff --git a/src/pages/tests.py b/src/pages/tests.py index 49290204..a39b155a 100644 --- a/src/pages/tests.py +++ b/src/pages/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/src/pages/urls.py b/src/pages/urls.py index 957447d7..b8303e59 100644 --- a/src/pages/urls.py +++ b/src/pages/urls.py @@ -4,8 +4,6 @@ from pages import views router = DefaultRouter() -router.register(r'', views.TagViewSet, basename='pages') +router.register(r"", views.TagViewSet, basename="pages") -urlpatterns = [ - path('', include(router.urls)) -] \ No newline at end of file +urlpatterns = [path("", include(router.urls))] diff --git a/src/pages/views.py b/src/pages/views.py index cb3dd273..a553467b 100644 --- a/src/pages/views.py +++ b/src/pages/views.py @@ -8,6 +8,6 @@ class TagViewSet(ModelViewSet): queryset = Page.objects.all() permission_classes = (AdminOrAnonymousReadOnly,) - throttle_scope = 'pages' + throttle_scope = "pages" serializer_class = PageSerializer pagination_class = None diff --git a/src/plugins/apps.py b/src/plugins/apps.py index 9ba404e7..c6b64af8 100644 --- a/src/plugins/apps.py +++ b/src/plugins/apps.py @@ -9,9 +9,9 @@ class PluginsConfig(AppConfig): class PluginConfig(AppConfig, abc.ABC): - def ready(self): from plugins import providers - if hasattr(self, 'provides') and isinstance(self.provides, list): + + if hasattr(self, "provides") and isinstance(self.provides, list): for provider in map(locate, self.provides): providers.register_provider(provider.type, provider()) diff --git a/src/plugins/flag/hashed.py b/src/plugins/flag/hashed.py index 5023862c..896fa23c 100644 --- a/src/plugins/flag/hashed.py +++ b/src/plugins/flag/hashed.py @@ -12,7 +12,5 @@ def check(self, flag, *args, **kwargs): def self_check(self): """Ensure the set flag metadata has a 'flag' property of length 64""" if len(self.challenge.flag_metadata.get("flag", "")) == 64: - return [ - "property 'flag' must be of length 64!" - ] - return [] \ No newline at end of file + return ["property 'flag' must be of length 64!"] + return [] diff --git a/src/plugins/flag/lenient.py b/src/plugins/flag/lenient.py index 7a977436..83f17c4d 100644 --- a/src/plugins/flag/lenient.py +++ b/src/plugins/flag/lenient.py @@ -4,7 +4,7 @@ def strip_accents(s): - return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn') + return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn") def lower(s): @@ -12,33 +12,33 @@ def lower(s): def strip_whitespace(s): - return ''.join(s.split()) + return "".join(s.split()) def fix_format(s): - prefix = config.config.get('flag_prefix') - return s if prefix + '{' in s else prefix + '{' + s + '}' + prefix = config.config.get("flag_prefix") + return s if prefix + "{" in s else prefix + "{" + s + "}" passes = { - 'accent_insensitive': strip_accents, - 'case_insensitive': lower, - 'whitespace_insensitive': strip_whitespace, - 'format': fix_format, + "accent_insensitive": strip_accents, + "case_insensitive": lower, + "whitespace_insensitive": strip_whitespace, + "format": fix_format, } class LenientFlagPlugin(FlagPlugin): - name = 'lenient' + name = "lenient" def check(self, flag, *args, **kwargs): flag_metadata = self.challenge.flag_metadata - if 'exclude_passes' not in flag_metadata: - flag_metadata['exclude_passes'] = [] + if "exclude_passes" not in flag_metadata: + flag_metadata["exclude_passes"] = [] - real_flag = flag_metadata['flag'] + real_flag = flag_metadata["flag"] for operation in passes: - if operation not in flag_metadata['exclude_passes']: + if operation not in flag_metadata["exclude_passes"]: flag = passes[operation](flag) real_flag = passes[operation](real_flag) return real_flag == flag @@ -46,7 +46,5 @@ def check(self, flag, *args, **kwargs): def self_check(self): """Ensure the set flag metadata has a 'flag' property""" if not self.challenge.flag_metadata.get("flag", ""): - return [ - "property 'flag' must be set!" - ] - return [] \ No newline at end of file + return ["property 'flag' must be set!"] + return [] diff --git a/src/plugins/flag/long_text.py b/src/plugins/flag/long_text.py index 779ae585..30e4d18f 100644 --- a/src/plugins/flag/long_text.py +++ b/src/plugins/flag/long_text.py @@ -18,7 +18,5 @@ def check(self, flag, *args, **kwargs): def self_check(self): """Ensure the set flag metadata has a 'flag' property""" if not self.challenge.flag_metadata.get("flag", ""): - return [ - "property 'flag' must be set!" - ] - return [] \ No newline at end of file + return ["property 'flag' must be set!"] + return [] diff --git a/src/plugins/flag/map.py b/src/plugins/flag/map.py index 3fec697f..d48c46f2 100644 --- a/src/plugins/flag/map.py +++ b/src/plugins/flag/map.py @@ -16,10 +16,7 @@ def check(self, flag, *args, **kwargs): lon_diff = lon2 - lon1 lat_diff = lat2 - lat1 - a = ( - math.sin(lat_diff / 2) ** 2 - + math.cos(lat1) * math.cos(lat2) * math.sin(lon_diff / 2) ** 2 - ) + a = math.sin(lat_diff / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(lon_diff / 2) ** 2 distance = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) * r return self.challenge.flag_metadata["radius"] > distance diff --git a/src/plugins/flag/plaintext.py b/src/plugins/flag/plaintext.py index 6f868544..6b906501 100644 --- a/src/plugins/flag/plaintext.py +++ b/src/plugins/flag/plaintext.py @@ -13,7 +13,7 @@ def self_check(self): return ["property 'flag' must be set!"] set_flag = self.challenge.flag_metadata["flag"] - if not set_flag.startswith(config.get('flag_prefix')): + if not set_flag.startswith(config.get("flag_prefix")): return ["flag does not conform to event flag format!"] return [] diff --git a/src/plugins/points/base.py b/src/plugins/points/base.py index 0ab9a325..9cbee06d 100644 --- a/src/plugins/points/base.py +++ b/src/plugins/points/base.py @@ -11,8 +11,8 @@ class PointsPlugin(Plugin, abc.ABC): - plugin_type = 'points' - recalculate_type = 'none' + plugin_type = "points" + recalculate_type = "none" def __init__(self, challenge): self.challenge = challenge @@ -28,28 +28,27 @@ def score(self, user, team, flag, solves, *args, **kwargs): challenge = self.challenge points = self.get_points(team, flag, solves.count()) if team is not None: - deducted = HintUse.objects.filter(team=team, challenge=challenge).aggregate(points=Sum(F('hint__penalty'))) + deducted = HintUse.objects.filter(team=team, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) else: - deducted = HintUse.objects.filter(user=user, challenge=challenge).aggregate(points=Sum(F('hint__penalty'))) - deducted = 0 if deducted['points'] is None else deducted['points'] + deducted = HintUse.objects.filter(user=user, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) + deducted = 0 if deducted["points"] is None else deducted["points"] deducted = min(points, deducted) - scored = config.config.get('end_time') >= time.time() and config.config.get('enable_scoring') - score = Score(team=team, reason='challenge', points=points, penalty=deducted, leaderboard=scored, user=user) + scored = config.config.get("end_time") >= time.time() and config.config.get("enable_scoring") + score = Score(team=team, reason="challenge", points=points, penalty=deducted, leaderboard=scored, user=user) score.save() - solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, - flag=flag, score=score) + solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, flag=flag, score=score) solve.save() - user.points += (points - deducted) + user.points += points - deducted if team is not None: - team.points += (points - deducted) + team.points += points - deducted if scored: - user.leaderboard_points += (points - deducted) + user.leaderboard_points += points - deducted if team is not None: - team.leaderboard_points += (points - deducted) + team.leaderboard_points += points - deducted user.last_score = timezone.now() team.last_score = timezone.now() return solve def register_incorrect_attempt(self, user, team, flag, solves, *args, **kwargs): - if config.config.get('enable_track_incorrect_submissions'): + if config.config.get("enable_track_incorrect_submissions"): Solve(team=team, solved_by=user, challenge=self.challenge, flag=flag, correct=False, score=None).save() diff --git a/src/polaris/admin.py b/src/polaris/admin.py index b97a94f6..846f6b40 100644 --- a/src/polaris/admin.py +++ b/src/polaris/admin.py @@ -1,2 +1 @@ - # Register your models here. diff --git a/src/polaris/apps.py b/src/polaris/apps.py index d045d68b..fcb173f2 100644 --- a/src/polaris/apps.py +++ b/src/polaris/apps.py @@ -2,4 +2,4 @@ class PolarisConfig(AppConfig): - name = 'polaris' + name = "polaris" diff --git a/src/polaris/client.py b/src/polaris/client.py index 62a4dce3..7d85a3d2 100644 --- a/src/polaris/client.py +++ b/src/polaris/client.py @@ -7,105 +7,96 @@ def handle_request(method, path, **kwargs): - response = requests.request(method, f"{settings.POLARIS_URL}/{path}", - auth=HTTPBasicAuth(settings.POLARIS_USERNAME, settings.POLARIS_PASSWORD), **kwargs) + response = requests.request(method, f"{settings.POLARIS_URL}/{path}", auth=HTTPBasicAuth(settings.POLARIS_USERNAME, settings.POLARIS_PASSWORD), **kwargs) if 200 <= response.status_code < 300: return response.json() elif response.status_code == HTTP_401_UNAUTHORIZED: - raise FormattedException(status_code=HTTP_401_UNAUTHORIZED, m='polaris_unauthorized') + raise FormattedException(status_code=HTTP_401_UNAUTHORIZED, m="polaris_unauthorized") elif response.status_code == HTTP_403_FORBIDDEN: - raise FormattedException(status_code=HTTP_403_FORBIDDEN, m='polaris_permission_denied') + raise FormattedException(status_code=HTTP_403_FORBIDDEN, m="polaris_permission_denied") else: - raise FormattedException(m='polaris_error', d=response.json()) + raise FormattedException(m="polaris_error", d=response.json()) def get(path, **kwargs): - return handle_request('GET', path, **kwargs) + return handle_request("GET", path, **kwargs) def post(path, **kwargs): - return handle_request('POST', path, **kwargs) + return handle_request("POST", path, **kwargs) def put(path, **kwargs): - return handle_request('PUT', path, **kwargs) + return handle_request("PUT", path, **kwargs) def delete(path, **kwargs): - return handle_request('DELETE', path, **kwargs) + return handle_request("DELETE", path, **kwargs) def list_challenges(filter=""): - return get(f'/challenges?filter={filter}') + return get(f"/challenges?filter={filter}") def get_challenge(challenge): - return get(f'/challenges/{challenge}') + return get(f"/challenges/{challenge}") def submit_challenge(challenge): - return post('/challenges', json=challenge) + return post("/challenges", json=challenge) def delete_challenge(challenge): - return post(f'/challenges/{challenge}') + return post(f"/challenges/{challenge}") def list_deployments(deployment_filter="", challenge_filter=""): - return get(f'/deployments?filter={deployment_filter}&challengefilter={challenge_filter}') + return get(f"/deployments?filter={deployment_filter}&challengefilter={challenge_filter}") def get_deployment(deployment): - return get(f'/deployments/{deployment}') + return get(f"/deployments/{deployment}") def submit_deployment(deployment): - return post('/deployments', json=deployment) + return post("/deployments", json=deployment) def delete_deployment(deployment): - return post(f'/deployments/{deployment}') + return post(f"/deployments/{deployment}") def list_hosts(filter=""): - return get(f'/hosts?filter={filter}') + return get(f"/hosts?filter={filter}") def get_host(id): - return get(f'/hosts/{id}') + return get(f"/hosts/{id}") def censor_instance(instance): - del instance['randomEnv'] + del instance["randomEnv"] ports = [] - for port in instance['ports']: - if port['advertised']: + for port in instance["ports"]: + if port["advertised"]: ports.append(port) - instance['ports'] = ports + instance["ports"] = ports return instance def allocate_instance(challenge_id, user): - response = post('/instanceallocation', json={ - 'challenge': str(challenge_id), - 'user': str(user.id), - 'team': str(user.team.id) - }) + response = post("/instanceallocation", json={"challenge": str(challenge_id), "user": str(user.id), "team": str(user.team.id)}) if not user.is_superuser: return censor_instance(response) return response def reallocate_instance(challenge_id, user): - response = post('/instanceallocation/new', json={ - 'challenge': str(challenge_id), - 'user': str(user.id), - 'team': str(user.team.id) - }) + response = post("/instanceallocation/new", json={"challenge": str(challenge_id), "user": str(user.id), "team": str(user.team.id)}) if not user.is_superuser: return censor_instance(response) return response def list_instances(host_filter="", challenge_filter=""): - return get(f'/instances?hostfilter={host_filter}&challengefilter={challenge_filter}') + return get(f"/instances?hostfilter={host_filter}&challengefilter={challenge_filter}") diff --git a/src/polaris/models.py b/src/polaris/models.py index 35e0d648..6b202199 100644 --- a/src/polaris/models.py +++ b/src/polaris/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/polaris/tests.py b/src/polaris/tests.py index 49290204..a39b155a 100644 --- a/src/polaris/tests.py +++ b/src/polaris/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/src/polaris/urls.py b/src/polaris/urls.py index 04f4aeb9..e23b1593 100644 --- a/src/polaris/urls.py +++ b/src/polaris/urls.py @@ -4,13 +4,13 @@ from polaris import views router = DefaultRouter() -router.register('challenge/', views.ChallengeViewset, basename='polaris-challenge') -router.register('deployment/', views.DeploymentViewset, basename='polaris-deployment') -router.register('host/', views.HostViewset, basename='polaris-host') +router.register("challenge/", views.ChallengeViewset, basename="polaris-challenge") +router.register("deployment/", views.DeploymentViewset, basename="polaris-deployment") +router.register("host/", views.HostViewset, basename="polaris-host") urlpatterns = [ - path('get_instance/', views.GetInstanceView.as_view(), name='polaris-get-instance'), - path('new_instance/', views.ResetInstanceView.as_view(), name='polaris-reset-instance'), - path('', include(router.urls), name='polaris'), - path('instances/', views.ListInstancesView.as_view(), name='polaris-list-instances'), + path("get_instance/", views.GetInstanceView.as_view(), name="polaris-get-instance"), + path("new_instance/", views.ResetInstanceView.as_view(), name="polaris-reset-instance"), + path("", include(router.urls), name="polaris"), + path("instances/", views.ListInstancesView.as_view(), name="polaris-list-instances"), ] diff --git a/src/polaris/views.py b/src/polaris/views.py index 212a4f32..228c53cd 100644 --- a/src/polaris/views.py +++ b/src/polaris/views.py @@ -29,7 +29,7 @@ class ChallengeViewset(viewsets.ViewSet): throttle_scope = "polaris_challenges" def list(self, request): - return FormattedResponse(client.list_challenges(request.GET.get('filter', ''))) + return FormattedResponse(client.list_challenges(request.GET.get("filter", ""))) def retrieve(self, request, pk): return FormattedResponse(client.get_challenge(pk)) @@ -38,12 +38,12 @@ def destroy(self, request, pk): return FormattedResponse(client.delete_challenge(pk)) def create(self, request): - challenge_id = request.GET.get('challenge_id', '') - if challenge_id == '': + challenge_id = request.GET.get("challenge_id", "") + if challenge_id == "": return FormattedResponse(client.submit_challenge(request.data)) challenge = get_object_or_404(Challenge.objects, challenge_id) response = client.submit_challenge(challenge) - challenge.challenge_metadata['cserv_name'] = response.data['id'] + challenge.challenge_metadata["cserv_name"] = response.data["id"] challenge.save() return response @@ -53,8 +53,7 @@ class DeploymentViewset(viewsets.ViewSet): throttle_scope = "polaris_deployments" def list(self, request): - return FormattedResponse(client.list_deployments(request.GET.get('deploymentfilter', ''), - request.GET.get('challengefilter', ''))) + return FormattedResponse(client.list_deployments(request.GET.get("deploymentfilter", ""), request.GET.get("challengefilter", ""))) def retrieve(self, request, pk): return FormattedResponse(client.get_deployment(pk)) @@ -71,7 +70,7 @@ class HostViewset(viewsets.ViewSet): throttle_scope = "polaris_hosts" def list(self, request): - return FormattedResponse(client.list_hosts(request.GET.get('filter', ''))) + return FormattedResponse(client.list_hosts(request.GET.get("filter", ""))) def retrieve(self, request, pk): return FormattedResponse(client.get_host(pk)) @@ -82,4 +81,4 @@ class ListInstancesView(APIView): throttle_scope = "polaris_view_instances" def get(self, request): - return FormattedResponse(client.list_instances(request.GET.get('hostfilter', ''), request.GET.get('challengefilter', ''))) + return FormattedResponse(client.list_instances(request.GET.get("hostfilter", ""), request.GET.get("challengefilter", ""))) diff --git a/src/ractf/apps.py b/src/ractf/apps.py index 22fc186c..9f39bdd3 100644 --- a/src/ractf/apps.py +++ b/src/ractf/apps.py @@ -12,10 +12,5 @@ def check_settings(app_configs, **kwargs): # pragma: no cover errors = [] for setting in settings.REQUIRED_SETTINGS: if getattr(settings, setting, None) is None: - errors.append(CheckMessage( - (WARNING if settings.DEBUG else ERROR), - f"Required setting {setting} was missing.", - hint="Did you forget to set an environment variable?", - id="ractf.E001" - )) + errors.append(CheckMessage((WARNING if settings.DEBUG else ERROR), f"Required setting {setting} was missing.", hint="Did you forget to set an environment variable?", id="ractf.E001")) return errors diff --git a/src/ractf/management/commands/copy_points.py b/src/ractf/management/commands/copy_points.py index 8b019ee0..dcb00736 100644 --- a/src/ractf/management/commands/copy_points.py +++ b/src/ractf/management/commands/copy_points.py @@ -10,7 +10,7 @@ class Command(BaseCommand): help = "Removes all scores from the database" def handle(self, *args, **options): - if time.time() > config.config.get('end_time'): + if time.time() > config.config.get("end_time"): return for score in Score.objects.all(): if not score.leaderboard: diff --git a/src/ractf/management/commands/flush_db.py b/src/ractf/management/commands/flush_db.py index adc8a725..19b2528d 100644 --- a/src/ractf/management/commands/flush_db.py +++ b/src/ractf/management/commands/flush_db.py @@ -11,17 +11,18 @@ class Command(BaseCommand): help = "Resets the database to the default configuration" def handle(self, *args, **options): - connection = psycopg2.connect(user=settings.DATABASES['default']['USER'], - password=settings.DATABASES['default']['PASSWORD'], - host=settings.DATABASES['default']['HOST'], - port=settings.DATABASES['default']['PORT'], - database='template1') + connection = psycopg2.connect( + user=settings.DATABASES["default"]["USER"], + password=settings.DATABASES["default"]["PASSWORD"], + host=settings.DATABASES["default"]["HOST"], + port=settings.DATABASES["default"]["PORT"], + database="template1", + ) connection.set_isolation_level(0) cursor = connection.cursor() - print('Dropping database...') - cursor.execute('DROP DATABASE "%s"' % settings.DATABASES['default']['NAME']) - print('Creating database...') - cursor.execute('CREATE DATABASE "%s" WITH OWNER = "%s" ENCODING = "UTF8"' % - (settings.DATABASES['default']['NAME'], settings.DATABASES['default']['USER'])) + print("Dropping database...") + cursor.execute('DROP DATABASE "%s"' % settings.DATABASES["default"]["NAME"]) + print("Creating database...") + cursor.execute('CREATE DATABASE "%s" WITH OWNER = "%s" ENCODING = "UTF8"' % (settings.DATABASES["default"]["NAME"], settings.DATABASES["default"]["USER"])) cursor.close() migrate.Command().run_from_argv(sys.argv) diff --git a/src/ractf/management/commands/getschema.py b/src/ractf/management/commands/getschema.py index 02b65438..6d9602dd 100644 --- a/src/ractf/management/commands/getschema.py +++ b/src/ractf/management/commands/getschema.py @@ -16,24 +16,26 @@ def handle(self, *args, **options): call_command("generateschema", stdout=file) file.seek(0) document = yaml.load(file, Loader=yaml.FullLoader) - document.update({ - "externalDocs": { - "description": "Check us out on GitHub", - "url": "https://github.com/ractf", - }, - "info": { - "title": "RACTF Core", - "version": os.popen("git rev-parse HEAD").read().strip()[:8], - "description": "The API for RACTF.", - "contact": { - "name": "Support", - "email": "support@ractf.co.uk", - "url": "https://reallyawesome.atlassian.net/servicedesk/customer/portals", + document.update( + { + "externalDocs": { + "description": "Check us out on GitHub", + "url": "https://github.com/ractf", }, - "x-logo": { - "url": "https://www.ractf.co.uk/brand_assets/combined/wordmark_white.svg", - "altText": "RACTF Logo", + "info": { + "title": "RACTF Core", + "version": os.popen("git rev-parse HEAD").read().strip()[:8], + "description": "The API for RACTF.", + "contact": { + "name": "Support", + "email": "support@ractf.co.uk", + "url": "https://reallyawesome.atlassian.net/servicedesk/customer/portals", + }, + "x-logo": { + "url": "https://www.ractf.co.uk/brand_assets/combined/wordmark_white.svg", + "altText": "RACTF Logo", + }, }, } - }) + ) print(yaml.dump(document)) diff --git a/src/ractf/management/commands/insert_dummy_data.py b/src/ractf/management/commands/insert_dummy_data.py index 81ccdba6..53d778fe 100644 --- a/src/ractf/management/commands/insert_dummy_data.py +++ b/src/ractf/management/commands/insert_dummy_data.py @@ -7,42 +7,41 @@ class Command(BaseCommand): - def handle(self, *args, **options): - category = Category(name='test', display_order=0, contained_type='test', description='') + category = Category(name="test", display_order=0, contained_type="test", description="") category.save() - challenge1 = Challenge(name='test1', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge2 = Challenge(name='test2', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) - challenge3 = Challenge(name='test3', category=category, description='a', challenge_type='basic', - challenge_metadata={}, flag_type='plaintext', flag_metadata={'flag': 'ractf{a}'}, - author='dave', score=1000) + challenge1 = Challenge( + name="test1", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000 + ) + challenge2 = Challenge( + name="test2", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000 + ) + challenge3 = Challenge( + name="test3", category=category, description="a", challenge_type="basic", challenge_metadata={}, flag_type="plaintext", flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000 + ) challenge1.save() challenge2.save() challenge3.save() challenge2.unlocks.add(challenge1) challenge2.save() - hint1 = Hint(name='hint1', challenge=challenge1, text='a', penalty=100) - hint2 = Hint(name='hint2', challenge=challenge1, text='a', penalty=100) - hint3 = Hint(name='hint3', challenge=challenge2, text='a', penalty=100) + hint1 = Hint(name="hint1", challenge=challenge1, text="a", penalty=100) + hint2 = Hint(name="hint2", challenge=challenge1, text="a", penalty=100) + hint3 = Hint(name="hint3", challenge=challenge2, text="a", penalty=100) hint1.save() hint2.save() hint3.save() - user = get_user_model()(username='test', email='challenge-test@example.org') + user = get_user_model()(username="test", email="challenge-test@example.org") user.save() - team = Team(name='team', password='password', owner=user) + team = Team(name="team", password="password", owner=user) team.save() user.team = team user.save() - user2 = get_user_model()(username='test-2', email='challenge-test-2@example.org') + user2 = get_user_model()(username="test-2", email="challenge-test-2@example.org") user2.team = team user2.save() - user3 = get_user_model()(username='test-3', email='challenge-test-3@example.org') + user3 = get_user_model()(username="test-3", email="challenge-test-3@example.org") user3.save() - team2 = Team(name='team2', password='password', owner=user3) + team2 = Team(name="team2", password="password", owner=user3) team2.save() user3.team = team2 user3.save() diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 1c16213e..dc8c477c 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -22,13 +22,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): class Command(BaseCommand): - def handle(self, *args, **options): with TimedLog("Inserting phat dummy data... ", ending="\n"): with TimedLog("Creating 10 categories..."): for i in range(10): - category = Category(name='category-' + str(i), display_order=i, contained_type='test', - description='Category ' + str(i)) + category = Category(name="category-" + str(i), display_order=i, contained_type="test", description="Category " + str(i)) category.save() def random_rpn_op(depth=0): @@ -42,25 +40,29 @@ def random_rpn_op(depth=0): else: return f"{random_rpn_op(depth)} {random_rpn_op(depth)} AND" - with TimedLog('Creating 100 challenges for each category...'): + with TimedLog("Creating 100 challenges for each category..."): for i in range(10): - category = Category.objects.get(name='category-' + str(i)) + category = Category.objects.get(name="category-" + str(i)) for j in range(100): - auto_unlock = (random.randint(1, 5) == 1) - challenge = Challenge(name='cat-' + str(i) + '-chal-' + str(j), category=category, - description='An example challenge ' + str(j), - flag_metadata={'flag': f'ractf{{{j}}}'}, author='dave', - score=j, challenge_metadata={}, - unlock_requirements=random_rpn_op() if not auto_unlock else "") + auto_unlock = random.randint(1, 5) == 1 + challenge = Challenge( + name="cat-" + str(i) + "-chal-" + str(j), + category=category, + description="An example challenge " + str(j), + flag_metadata={"flag": f"ractf{{{j}}}"}, + author="dave", + score=j, + challenge_metadata={}, + unlock_requirements=random_rpn_op() if not auto_unlock else "", + ) challenge.save() with TimedLog("Creating 20k users in memory..."): Member = get_user_model() users_to_create = [] for i in range(10000): - user = Member(username='user-' + str(i), email='user-' + str(i) + '@example.org') - user2 = Member(username='user-' + str(i) + '-second', - email='user-' + str(i) + '-second@example.org') + user = Member(username="user-" + str(i), email="user-" + str(i) + "@example.org") + user2 = Member(username="user-" + str(i) + "-second", email="user-" + str(i) + "-second@example.org") users_to_create.append(user) users_to_create.append(user2) @@ -71,7 +73,7 @@ def random_rpn_op(depth=0): teams_to_create = [] members = list(Member.objects.all()) for i in range(10000): - team = Team(name='team-' + str(i), password='password', owner=members[i * 2]) + team = Team(name="team-" + str(i), password="password", owner=members[i * 2]) teams_to_create.append(team) @@ -83,7 +85,7 @@ def random_rpn_op(depth=0): teams = list(Team.objects.all()) for i in range(0, len(members), 2): owner = members[i] - teammate = members[i+1] + teammate = members[i + 1] owner.team = teams[i // 2] teammate.team = teams[i // 2] @@ -106,10 +108,9 @@ def random_rpn_op(depth=0): used = [] for j in range(50): points = random.randint(0, 999) - score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) + score = Score(team=team, reason="challenge", points=points, penalty=0, leaderboard=True) scores_to_create.append(score) - solve = Solve(team=team, solved_by=user, challenge_id=(j * 19) + (team.id % 20) + 1, first_blood=False, - flag='ractf{}', score=score, correct=True) + solve = Solve(team=team, solved_by=user, challenge_id=(j * 19) + (team.id % 20) + 1, first_blood=False, flag="ractf{}", score=score, correct=True) solves_to_create.append(solve) user.points += points team.points += points @@ -117,10 +118,9 @@ def random_rpn_op(depth=0): team.leaderboard_points += points points = random.randint(0, 999) - score = Score(team=team, reason='challenge', points=points, penalty=0, leaderboard=True) + score = Score(team=team, reason="challenge", points=points, penalty=0, leaderboard=True) scores_to_create.append(score) - solve = Solve(team=team, solved_by=user2, challenge_id=(j * 19) + (team.id % 20) + 2, - first_blood=False, flag='ractf{}', score=score, correct=True) + solve = Solve(team=team, solved_by=user2, challenge_id=(j * 19) + (team.id % 20) + 2, first_blood=False, flag="ractf{}", score=score, correct=True) solves_to_create.append(solve) user2.points += points team.points += points diff --git a/src/ractf/management/commands/transfer.py b/src/ractf/management/commands/transfer.py index d9124b77..9a2c848a 100644 --- a/src/ractf/management/commands/transfer.py +++ b/src/ractf/management/commands/transfer.py @@ -8,11 +8,11 @@ class Command(BaseCommand): help = "Transfer team owner" def add_arguments(self, parser): - parser.add_argument('user_id', type=int) - parser.add_argument('team_id', type=int) + parser.add_argument("user_id", type=int) + parser.add_argument("team_id", type=int) def handle(self, *args, **options): - user = get_user_model().objects.get(pk=options['user_id']) - team = Team.objects.get(pk=options['team_id']) + user = get_user_model().objects.get(pk=options["user_id"]) + team = Team.objects.get(pk=options["team_id"]) team.owner = user team.save() diff --git a/src/ractf/management/commands/unteam.py b/src/ractf/management/commands/unteam.py index a2b2a70c..3bbc3222 100644 --- a/src/ractf/management/commands/unteam.py +++ b/src/ractf/management/commands/unteam.py @@ -6,10 +6,10 @@ class Command(BaseCommand): help = "Removes all scores from the database" def add_arguments(self, parser): - parser.add_argument('user_id', type=int) + parser.add_argument("user_id", type=int) def handle(self, *args, **options): - user = get_user_model().objects.get(pk=options['user_id']) + user = get_user_model().objects.get(pk=options["user_id"]) print("Choices:", user.team.members) if user.team.owner == user: x = input("This will delete the team, are you sure?") diff --git a/src/ractf/models.py b/src/ractf/models.py index 35e0d648..6b202199 100644 --- a/src/ractf/models.py +++ b/src/ractf/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/ractf/tests.py b/src/ractf/tests.py index 49290204..a39b155a 100644 --- a/src/ractf/tests.py +++ b/src/ractf/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/src/scorerecalculator/models.py b/src/scorerecalculator/models.py index 35e0d648..6b202199 100644 --- a/src/scorerecalculator/models.py +++ b/src/scorerecalculator/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/src/scorerecalculator/tests.py b/src/scorerecalculator/tests.py index af56b803..326405ca 100644 --- a/src/scorerecalculator/tests.py +++ b/src/scorerecalculator/tests.py @@ -11,9 +11,7 @@ class RecalculateUserViewTestCase(APITestCase): def setUp(self): - user = get_user_model()( - username="recalculate-test", email="recalculate-test@example.org" - ) + user = get_user_model()(username="recalculate-test", email="recalculate-test@example.org") user.save() admin_user = get_user_model()( username="recalculate-test-admin", @@ -30,23 +28,17 @@ def setUp(self): self.team = team def test_unauthed(self): - response = self.client.post( - reverse("recalculate-user", kwargs={"id": self.user.id}) - ) + response = self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("recalculate-user", kwargs={"id": self.user.id}) - ) + response = self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_not_admin(self): self.client.force_authenticate(self.user) - response = self.client.post( - reverse("recalculate-user", kwargs={"id": self.user.id}) - ) + response = self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_recalculate(self): @@ -55,14 +47,10 @@ def test_recalculate(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) - self.assertEqual( - get_user_model().objects.get(id=self.user.id).points, total + 100 - ) + self.assertEqual(get_user_model().objects.get(id=self.user.id).points, total + 100) def test_recalculate_leaderboard(self): total = 0 @@ -70,21 +58,15 @@ def test_recalculate_leaderboard(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-user", kwargs={"id": self.user.id})) - self.assertEqual( - get_user_model().objects.get(id=self.user.id).leaderboard_points, total - ) + self.assertEqual(get_user_model().objects.get(id=self.user.id).leaderboard_points, total) class RecalculateTeamViewTestCase(APITestCase): def setUp(self): - user = get_user_model()( - username="recalculate-test", email="recalculate-test@example.org" - ) + user = get_user_model()(username="recalculate-test", email="recalculate-test@example.org") user.save() admin_user = get_user_model()( username="recalculate-test-admin", @@ -101,23 +83,17 @@ def setUp(self): self.team = team def test_unauthed(self): - response = self.client.post( - reverse("recalculate-team", kwargs={"id": self.team.id}) - ) + response = self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_authed(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("recalculate-team", kwargs={"id": self.team.id}) - ) + response = self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_not_admin(self): self.client.force_authenticate(self.user) - response = self.client.post( - reverse("recalculate-team", kwargs={"id": self.team.id}) - ) + response = self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_recalculate(self): @@ -126,9 +102,7 @@ def test_recalculate(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) self.assertEqual(Team.objects.get(id=self.team.id).points, total + 100) @@ -139,9 +113,7 @@ def test_recalculate_leaderboard(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-team", kwargs={"id": self.team.id})) self.assertEqual(Team.objects.get(id=self.team.id).leaderboard_points, total) @@ -149,9 +121,7 @@ def test_recalculate_leaderboard(self): class RecalculateAllViewTestCase(APITestCase): def setUp(self): - user = get_user_model()( - username="recalculate-test", email="recalculate-test@example.org" - ) + user = get_user_model()(username="recalculate-test", email="recalculate-test@example.org") user.save() admin_user = get_user_model()( username="recalculate-test-admin", @@ -187,15 +157,11 @@ def test_recalculate(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-all")) self.assertEqual(Team.objects.get(id=self.team.id).points, total + 100) - self.assertEqual( - get_user_model().objects.get(id=self.user.id).points, total + 100 - ) + self.assertEqual(get_user_model().objects.get(id=self.user.id).points, total + 100) def test_recalculate_leaderboard(self): total = 0 @@ -203,12 +169,8 @@ def test_recalculate_leaderboard(self): points = random.randint(0, 100) total += points Score(team=self.team, user=self.user, reason="test", points=points).save() - Score( - team=self.team, user=self.user, reason="test", points=100, leaderboard=False - ).save() + Score(team=self.team, user=self.user, reason="test", points=100, leaderboard=False).save() self.client.force_authenticate(self.admin_user) self.client.post(reverse("recalculate-all")) self.assertEqual(Team.objects.get(id=self.team.id).leaderboard_points, total) - self.assertEqual( - get_user_model().objects.get(id=self.user.id).leaderboard_points, total - ) + self.assertEqual(get_user_model().objects.get(id=self.user.id).leaderboard_points, total) diff --git a/src/scorerecalculator/urls.py b/src/scorerecalculator/urls.py index ed39e897..0741be61 100644 --- a/src/scorerecalculator/urls.py +++ b/src/scorerecalculator/urls.py @@ -3,7 +3,7 @@ from scorerecalculator import views urlpatterns = [ - path('team//', views.RecalculateTeamView.as_view(), name='recalculate-team'), - path('user//', views.RecalculateUserView.as_view(), name='recalculate-user'), - path('', views.RecalculateAllView.as_view(), name='recalculate-all'), + path("team//", views.RecalculateTeamView.as_view(), name="recalculate-team"), + path("user//", views.RecalculateUserView.as_view(), name="recalculate-user"), + path("", views.RecalculateAllView.as_view(), name="recalculate-all"), ] diff --git a/src/scorerecalculator/views.py b/src/scorerecalculator/views.py index 9840d865..d3aed346 100644 --- a/src/scorerecalculator/views.py +++ b/src/scorerecalculator/views.py @@ -47,9 +47,7 @@ class RecalculateUserView(APIView): def post(self, request, id): with transaction.atomic(): - user = get_object_or_404( - get_user_model().objects.select_for_update(), id=id - ) + user = get_object_or_404(get_user_model().objects.select_for_update(), id=id) recalculate_user(user) return FormattedResponse() diff --git a/src/sockets/routing.py b/src/sockets/routing.py index 4e472e50..2a280c8f 100644 --- a/src/sockets/routing.py +++ b/src/sockets/routing.py @@ -4,10 +4,4 @@ from sockets import consumers -application = ProtocolTypeRouter( - { - "websocket": AuthMiddlewareStack( - URLRouter([re_path(r"^ws/$", consumers.EventConsumer), re_path(r"^api/v2/ws/$", consumers.EventConsumer)]) - ) - } -) +application = ProtocolTypeRouter({"websocket": AuthMiddlewareStack(URLRouter([re_path(r"^ws/$", consumers.EventConsumer), re_path(r"^api/v2/ws/$", consumers.EventConsumer)]))}) diff --git a/src/sockets/signals.py b/src/sockets/signals.py index d7707f8d..82402b96 100644 --- a/src/sockets/signals.py +++ b/src/sockets/signals.py @@ -11,7 +11,7 @@ def get_team_channel(user): - return f'team.{user.team.id}' + return f"team.{user.team.id}" def send(user, data): @@ -21,80 +21,87 @@ def send(user, data): def broadcast(data): channel_layer = get_channel_layer() - async_to_sync(channel_layer.group_send)('event', data) + async_to_sync(channel_layer.group_send)("event", data) @receiver(flag_score) def on_flag_score(user, team, challenge, flag, solve, **kwargs): - if not config.config.get('enable_solve_broadcast'): + if not config.config.get("enable_solve_broadcast"): return - broadcast({ - 'type': 'send_json', - 'event_code': 1, - 'user': user.id, - 'username': user.username, - 'challenge_id': challenge.id, - 'challenge_name': challenge.name, - 'challenge_score': solve.score.points, - 'team': team.id, - 'team_name': team.name, - }) + broadcast( + { + "type": "send_json", + "event_code": 1, + "user": user.id, + "username": user.username, + "challenge_id": challenge.id, + "challenge_name": challenge.name, + "challenge_score": solve.score.points, + "team": team.id, + "team_name": team.name, + } + ) @receiver(flag_reject) def on_flag_reject(user, team, challenge, flag, **kwargs): - send(user, { - 'type': 'send_json', - 'event_code': 2, - 'user': user.id, - 'username': user.username, - 'challenge_id': challenge.id, - 'challenge_name': challenge.name, - 'team': team.id, - 'team_name': team.name, - }) + send( + user, + { + "type": "send_json", + "event_code": 2, + "user": user.id, + "username": user.username, + "challenge_id": challenge.id, + "challenge_name": challenge.name, + "team": team.id, + "team_name": team.name, + }, + ) @receiver(use_hint) def on_use_hint(user, team, hint, **kwargs): - send(user, { - 'type': 'send_json', - 'event_code': 3, - 'user': user.id, - 'username': user.username, - 'team': team.id, - 'team_name': team.name, - 'hint_name': hint.name, - 'hint_penalty': hint.penalty, - 'hint_text': hint.text, - 'challenge': hint.challenge.name, - }) + send( + user, + { + "type": "send_json", + "event_code": 3, + "user": user.id, + "username": user.username, + "team": team.id, + "team_name": team.name, + "hint_name": hint.name, + "hint_penalty": hint.penalty, + "hint_text": hint.text, + "challenge": hint.challenge.name, + }, + ) @receiver(team_join) def on_team_join(user, team, **kwargs): - send(user, { - 'type': 'send_json', - 'event_code': 4, - 'user': user.id, - 'username': user.username, - 'team': team.id, - 'team_name': team.name, - }) + send( + user, + { + "type": "send_json", + "event_code": 4, + "user": user.id, + "username": user.username, + "team": team.id, + "team_name": team.name, + }, + ) @receiver(post_save, sender=Announcement) def on_announcement_create(sender, instance, **kwargs): data = AnnouncementSerializer(instance).data - data['type'] = 'send_json' - data['event_code'] = 5 + data["type"] = "send_json" + data["event_code"] = 5 broadcast(data) @receiver(post_save, sender=Challenge) def on_challenge_edit(sender, instance, **kwargs): - broadcast({ - "type": "send_json", - "event_code": 6, - "challenge_id": instance.id - }) + broadcast({"type": "send_json", "event_code": 6, "challenge_id": instance.id}) diff --git a/src/stats/tests.py b/src/stats/tests.py index c6027de6..2396ce34 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -14,9 +14,7 @@ def test_unauthed(self): self.assertEqual(response.status_code, HTTP_200_OK) def test_authed(self): - user = get_user_model()( - username="countdown-test", email="countdown-test@example.org" - ) + user = get_user_model()(username="countdown-test", email="countdown-test@example.org") user.save() self.client.force_authenticate(user) response = self.client.get(reverse("countdown")) @@ -98,16 +96,16 @@ def test_challenge_data(self): flag_type="none", flag_metadata={}, author="test author", - score=5 + score=5, ) Solve.objects.create(challenge=chall, flag="", correct=True) Solve.objects.create(challenge=chall, flag="", correct=False) self.client.force_authenticate(user) - config.config.set('enable_caching', False) + config.config.set("enable_caching", False) response = self.client.get(reverse("full")) - config.config.set('enable_caching', True) + config.config.set("enable_caching", True) self.assertEqual(response.data["d"]["challenges"][chall.id]["incorrect"], 1) self.assertEqual(response.data["d"]["challenges"][chall.id]["correct"], 1) diff --git a/src/stats/urls.py b/src/stats/urls.py index 72ca837d..5e103fda 100644 --- a/src/stats/urls.py +++ b/src/stats/urls.py @@ -3,10 +3,9 @@ from stats import views urlpatterns = [ - path('countdown/', views.countdown, name='countdown'), - path('stats/', views.stats, name='stats'), - path('version/', views.version, name='version'), - path('full/', views.full, name='full'), - - path('prometheus/', views.PrometheusMetricsView.as_view(), name='prometheus') + path("countdown/", views.countdown, name="countdown"), + path("stats/", views.stats, name="stats"), + path("version/", views.version, name="version"), + path("full/", views.full, name="full"), + path("prometheus/", views.PrometheusMetricsView.as_view(), name="prometheus"), ] diff --git a/src/team/models.py b/src/team/models.py index 78340841..8babd5a4 100644 --- a/src/team/models.py +++ b/src/team/models.py @@ -27,9 +27,7 @@ def ranked(self) -> "models.QuerySet[Team]": def prefetch_solves(self) -> "models.QuerySet[Team]": """Prefetch this team's correct solves.""" - return self.prefetch_related( - Prefetch("solves", queryset=Solve.objects.filter(correct=True)) - ) + return self.prefetch_related(Prefetch("solves", queryset=Solve.objects.filter(correct=True))) class Team(ExportModelOperationsMixin("team"), models.Model): diff --git a/src/team/permissions.py b/src/team/permissions.py index d12ce749..cbf40281 100644 --- a/src/team/permissions.py +++ b/src/team/permissions.py @@ -5,10 +5,7 @@ class IsTeamOwnerOrReadOnly(permissions.BasePermission): def has_permission(self, request, view): - return ( - request.method in permissions.SAFE_METHODS - or request.user.team.owner == request.user - ) + return request.method in permissions.SAFE_METHODS or request.user.team.owner == request.user class HasTeam(permissions.BasePermission): diff --git a/src/team/serializers.py b/src/team/serializers.py index 959f9b66..4c6ad73f 100644 --- a/src/team/serializers.py +++ b/src/team/serializers.py @@ -101,9 +101,7 @@ class Meta: def create(self, validated_data): name = validated_data["name"] password = validated_data["password"] - team = Team.objects.create( - name=name, password=password, owner=self.context["request"].user - ) + team = Team.objects.create(name=name, password=password, owner=self.context["request"].user) self.context["request"].user.team = team self.context["request"].user.save() team_create.send(sender=self.__class__, team=team) diff --git a/src/team/tests.py b/src/team/tests.py index 9e54b8a4..64a6cde9 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -17,19 +17,13 @@ class TeamSetupMixin: def setUp(self): - self.user = get_user_model()( - username="team-test", email="team-test@example.org", is_visible=True - ) + self.user = get_user_model()(username="team-test", email="team-test@example.org", is_visible=True) self.user.save() - self.team = Team( - name="team-test", password="abc", description="", owner=self.user, is_visible=True - ) + self.team = Team(name="team-test", password="abc", description="", owner=self.user, is_visible=True) self.team.save() self.user.team = self.team self.user.save() - self.admin_user = get_user_model()( - username="team-test-admin", email="team-test-admin@example.org", is_visible=True - ) + self.admin_user = get_user_model()(username="team-test-admin", email="team-test-admin@example.org", is_visible=True) self.admin_user.is_staff = True self.admin_user.is_superuser = True self.admin_user.save() @@ -59,7 +53,7 @@ def test_update(self): self.client.force_authenticate(user=self.user) response = self.client.patch(reverse("team-self"), data={"name": "name-change"}) self.assertEqual(response.status_code, HTTP_200_OK) - self.assertEqual(response.data['name'], "name-change") + self.assertEqual(response.data["name"], "name-change") def test_update_not_owner(self): self.admin_user.team = self.team @@ -90,7 +84,7 @@ def test_team_leave_challenge_solved(self): flag_type="none", flag_metadata={}, author="test author", - score=5 + score=5, ) Solve.objects.create(solved_by=self.user, flag="", challenge=chall) @@ -122,100 +116,70 @@ def test_team_leave_as_owner_without_members(self): class CreateTeamTestCase(TeamSetupMixin, APITestCase): def test_create_team(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("team-create"), data={"name": "test-team", "password": "test"} - ) + response = self.client.post(reverse("team-create"), data={"name": "test-team", "password": "test"}) self.assertEqual(response.status_code, HTTP_201_CREATED) def test_create_team_in_team(self): self.client.force_authenticate(self.user) - response = self.client.post( - reverse("team-create"), data={"name": "test-team", "password": "test"} - ) + response = self.client.post(reverse("team-create"), data={"name": "test-team", "password": "test"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create_team_not_authed(self): - response = self.client.post( - reverse("team-create"), data={"name": "test-team", "password": "test"} - ) + response = self.client.post(reverse("team-create"), data={"name": "test-team", "password": "test"}) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_create_duplicate_team(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("team-create"), data={"name": "team-test", "password": "test"} - ) + response = self.client.post(reverse("team-create"), data={"name": "team-test", "password": "test"}) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) class JoinTeamTestCase(TeamSetupMixin, APITestCase): def test_join_team(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_200_OK) def test_join_team_incorrect_password(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "incorrect_pass"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "incorrect_pass"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_incorrect_name(self): self.client.force_authenticate(self.admin_user) - response = self.client.post( - reverse("team-join"), data={"name": "incorrect-team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "incorrect-team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_join_team_full(self): - user2 = get_user_model()( - username="team-test2", email="team-test2@example.org", is_visible=True - ) + user2 = get_user_model()(username="team-test2", email="team-test2@example.org", is_visible=True) user2.save() self.client.force_authenticate(self.admin_user) config.config.set("team_size", 1) - self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.client.force_authenticate(user2) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_disabled(self): self.client.force_authenticate(self.admin_user) config.config.set("enable_team_join", False) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) config.config.set("enable_team_join", True) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_duplicate(self): self.client.force_authenticate(self.admin_user) - self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_not_authed(self): - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_join_team_team_owner(self): self.client.force_authenticate(self.user) - response = self.client.post( - reverse("team-join"), data={"name": "team-test", "password": "abc"} - ) + response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_malformed(self): @@ -230,7 +194,7 @@ def test_visible_admin(self): self.team.save() self.client.force_authenticate(self.admin_user) response = self.client.get(reverse("team-list")) - self.assertEqual(len(response.data['d']["results"]), 1) + self.assertEqual(len(response.data["d"]["results"]), 1) def test_visible_not_admin(self): self.team.is_visible = False @@ -273,14 +237,10 @@ def test_view_team(self): def test_patch_team(self): self.client.force_authenticate(self.user) - response = self.client.patch( - reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"} - ) + response = self.client.patch(reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"}) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_patch_team_admin(self): self.client.force_authenticate(self.admin_user) - response = self.client.patch( - reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"} - ) + response = self.client.patch(reverse("team-detail", kwargs={"pk": self.team.id}), data={"name": "test"}) self.assertEqual(response.status_code, HTTP_200_OK) diff --git a/src/team/urls.py b/src/team/urls.py index f15db656..a40d88ab 100644 --- a/src/team/urls.py +++ b/src/team/urls.py @@ -4,12 +4,12 @@ from team import views router = DefaultRouter() -router.register(r'', views.TeamViewSet, basename='team') +router.register(r"", views.TeamViewSet, basename="team") urlpatterns = [ - path('self/', views.SelfView.as_view(), name='team-self'), - path('create/', views.CreateTeamView.as_view(), name='team-create'), - path('join/', views.JoinTeamView.as_view(), name='team-join'), - path('leave/', views.LeaveTeamView.as_view(), name='team-leave'), - path('', include(router.urls), name='team'), + path("self/", views.SelfView.as_view(), name="team-self"), + path("create/", views.CreateTeamView.as_view(), name="team-create"), + path("join/", views.JoinTeamView.as_view(), name="team-join"), + path("leave/", views.LeaveTeamView.as_view(), name="team-leave"), + path("", include(router.urls), name="team"), ] diff --git a/src/team/views.py b/src/team/views.py index d7afc672..d6f1e04c 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -46,7 +46,8 @@ def get_object(self): "solves__challenge", "solves__score", "solves__solved_by", - ).get(id=self.request.user.team.id) + ) + .get(id=self.request.user.team.id) ) @@ -110,18 +111,18 @@ def post(self, request): team = get_object_or_404(Team, name=name) if team.password != password: team_join_reject.send(sender=self.__class__, user=request.user, name=name) - raise FormattedException(m='invalid_team_password', status=HTTP_403_FORBIDDEN) + raise FormattedException(m="invalid_team_password", status=HTTP_403_FORBIDDEN) except Http404: team_join_reject.send(sender=self.__class__, user=request.user, name=name) - raise FormattedException(m='invalid_team', status=HTTP_404_NOT_FOUND) - team_size = int(config.config.get('team_size')) + raise FormattedException(m="invalid_team", status=HTTP_404_NOT_FOUND) + team_size = int(config.config.get("team_size")) if not request.user.is_staff and not team.size_limit_exempt and 0 < team_size <= team.members.count(): - return FormattedResponse(m='team_full', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="team_full", status=HTTP_403_FORBIDDEN) request.user.team = team request.user.save() team_join.send(sender=self.__class__, user=request.user, team=team) return FormattedResponse() - return FormattedResponse(m='joined_team', status=HTTP_400_BAD_REQUEST) + return FormattedResponse(m="joined_team", status=HTTP_400_BAD_REQUEST) class LeaveTeamView(APIView): @@ -132,13 +133,13 @@ class LeaveTeamView(APIView): permission_classes = (IsAuthenticated & HasTeam & TeamsEnabled,) def post(self, request): - if not config.config.get('enable_team_leave'): - return FormattedResponse(m='leave_disabled', status=HTTP_403_FORBIDDEN) + if not config.config.get("enable_team_leave"): + return FormattedResponse(m="leave_disabled", status=HTTP_403_FORBIDDEN) if Solve.objects.filter(solved_by=request.user).exists(): - return FormattedResponse(m='challenge_solved', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="challenge_solved", status=HTTP_403_FORBIDDEN) if request.user.team.owner == request.user: if Member.objects.filter(team=request.user.team).count() > 1: - return FormattedResponse(m='cannot_leave_team_ownerless', status=HTTP_403_FORBIDDEN) + return FormattedResponse(m="cannot_leave_team_ownerless", status=HTTP_403_FORBIDDEN) else: request.user.team.delete() request.user.team = None From b3de0443747152a33c41a64b114d50f513d9a1e1 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Jun 2021 17:16:12 +0100 Subject: [PATCH 061/185] cleanup some merge conflict stuff --- src/challenge/serializers.py | 174 +---------------------------------- src/challenge/views.py | 2 - 2 files changed, 2 insertions(+), 174 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 24b07469..b835d130 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,11 +1,9 @@ -from collections import Counter - import serpy from rest_framework import serializers -from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag, ChallengeVote +from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag from challenge.sql import get_solve_counts, get_positive_votes, get_negative_votes -from hint.serializers import HintSerializer, FastHintSerializer +from hint.serializers import FastHintSerializer def setup_context(context): @@ -60,12 +58,6 @@ class FastFileSerializer(serpy.Serializer): challenge = ForeignKeyField() -class NestedTagSerializer(serializers.ModelSerializer): - class Meta: - model = Tag - fields = ["text", "type"] - - class FastNestedTagSerializer(serpy.Serializer): text = serpy.StrField() type = serpy.StrField() @@ -128,112 +120,6 @@ class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): post_score_explanation = serpy.StrField() -class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - unlock_time_surpassed = serializers.SerializerMethodField() - - class Meta: - model = Challenge - fields = ["id", "unlock_requirements", "challenge_metadata", "challenge_type", "hidden", "unlock_time_surpassed", "release_time"] - - -class ChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - hints = HintSerializer(many=True, read_only=True) - files = FileSerializer(many=True, read_only=True) - solved = serializers.SerializerMethodField() - unlocked = serializers.SerializerMethodField() - unlock_time_surpassed = serializers.SerializerMethodField() - votes = serializers.SerializerMethodField() - first_blood_name = serializers.ReadOnlyField(source="first_blood.username") - solve_count = serializers.SerializerMethodField() - tags = NestedTagSerializer(many=True, read_only=True) - post_score_explanation = serializers.SerializerMethodField() - - -class LockedChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - unlock_time_surpassed = serializers.SerializerMethodField() - - class Meta: - model = Challenge - fields = ["id", "unlock_requirements", "challenge_metadata", "challenge_type", "hidden", "unlock_time_surpassed", "release_time", "score"] - - def __init__(self, *args, **kwargs): - super(FastChallengeSerializer, self).__init__(*args, **kwargs) - if "context" in kwargs: - self.context = kwargs["context"] - if "solve_counter" not in self.context: - setup_context(self.context) - - class Meta: - model = Challenge - fields = [ - "id", - "name", - "category", - "description", - "challenge_type", - "challenge_metadata", - "flag_type", - "author", - "auto_unlock", - "score", - "unlock_requirements", - "hints", - "files", - "solved", - "unlocked", - "first_blood", - "first_blood_name", - "solve_count", - "hidden", - "votes", - "tags", - "unlock_time_surpassed", - "post_score_explanation", - ] - - class Meta: - model = Challenge - fields = [ - "id", - "name", - "category", - "description", - "challenge_type", - "challenge_metadata", - "flag_type", - "author", - "score", - "unlock_requirements", - "hints", - "files", - "solved", - "unlocked", - "first_blood", - "first_blood_name", - "solve_count", - "hidden", - "votes", - "tags", - "unlock_time_surpassed", - "post_score_explanation", - ] - - def serialize(self, instance): - if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and not instance.hidden and instance.unlock_time_surpassed: - return super(FastChallengeSerializer, FastChallengeSerializer(instance, context=self.context)).to_value(instance) - return FastLockedChallengeSerializer(instance).data - - def to_representation(self, instance): - if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and not instance.hidden and instance.unlock_time_surpassed: - return super(ChallengeSerializer, self).to_representation(instance) - return LockedChallengeSerializer(instance).to_representation(instance) - - def to_value(self, instance): - if self.many: - return [self.serialize(o) for o in instance] - return self.serialize(instance) - - class FastCategorySerializer(serpy.Serializer): id = serpy.IntField() name = serpy.StrField() @@ -310,45 +196,6 @@ def to_value(self, instance): return self.serialize(instance) -class AdminChallengeSerializer(ChallengeSerializerMixin, serializers.ModelSerializer): - hints = HintSerializer(many=True, read_only=True) - files = FileSerializer(many=True, read_only=True) - solved = serializers.SerializerMethodField() - unlocked = serializers.SerializerMethodField() - votes = serializers.SerializerMethodField() - first_blood_name = serializers.ReadOnlyField(source="first_blood.team.name") - solve_count = serializers.SerializerMethodField() - tags = NestedTagSerializer(many=True, read_only=False) - - class Meta: - model = Challenge - fields = [ - "id", - "name", - "category", - "description", - "challenge_type", - "challenge_metadata", - "flag_type", - "author", - "score", - "unlock_requirements", - "flag_metadata", - "hints", - "files", - "solved", - "unlocked", - "first_blood", - "first_blood_name", - "solve_count", - "hidden", - "release_time", - "votes", - "post_score_explanation", - "tags", - ] - - class CreateChallengeSerializer(serializers.ModelSerializer): tags = serializers.ListField(write_only=True) @@ -389,23 +236,6 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) -class AdminCategorySerializer(serializers.ModelSerializer): - challenges = AdminChallengeSerializer(many=True, read_only=True) - - class Meta: - model = Category - fields = [ - "id", - "name", - "display_order", - "contained_type", - "description", - "metadata", - "challenges", - "release_time", - ] - - class FastAdminCategorySerializer(serpy.Serializer): id = serpy.IntField() name = serpy.StrField() diff --git a/src/challenge/views.py b/src/challenge/views.py index 5a168450..5d66d0c5 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -25,7 +25,6 @@ from challenge.models import Challenge, Category, Solve, File, ChallengeVote, ChallengeFeedback, Tag, Score from challenge.permissions import CompetitionOpen from challenge.serializers import ( - AdminCategorySerializer, FileSerializer, CreateCategorySerializer, CreateChallengeSerializer, @@ -42,7 +41,6 @@ ) import config from hint.models import Hint, HintUse -from plugins import plugins from team.models import Team from team.permissions import HasTeam From 664fcb6afb48d62bd1b27f30a4243162e875ce9d Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Jun 2021 17:29:17 +0100 Subject: [PATCH 062/185] refactor config --- src/authentication/providers.py | 4 +- src/authentication/serializers.py | 12 ++-- src/authentication/tests.py | 30 +++++----- src/backend/authentication.py | 6 +- src/backend/permissions.py | 4 +- src/challenge/models.py | 4 +- src/challenge/permissions.py | 4 +- src/challenge/sql.py | 10 ++-- src/challenge/tests/test_views.py | 6 +- src/challenge/views.py | 10 ++-- src/config/__init__.py | 6 -- src/config/apps.py | 5 ++ src/config/config.py | 18 +++--- src/config/tests.py | 4 +- src/config/views.py | 16 +++--- src/leaderboard/tests.py | 58 ++++++++++---------- src/leaderboard/views.py | 14 ++--- src/member/models.py | 2 +- src/member/serializers.py | 4 +- src/plugins/flag/lenient.py | 4 +- src/plugins/points/base.py | 6 +- src/plugins/providers.py | 4 +- src/plugins/tests.py | 4 +- src/ractf/management/commands/copy_points.py | 4 +- src/sockets/signals.py | 4 +- src/stats/tests.py | 6 +- src/stats/views.py | 8 +-- src/team/permissions.py | 4 +- src/team/tests.py | 24 ++++---- src/team/views.py | 8 +-- 30 files changed, 144 insertions(+), 149 deletions(-) diff --git a/src/authentication/providers.py b/src/authentication/providers.py index d47174fc..36e6c773 100644 --- a/src/authentication/providers.py +++ b/src/authentication/providers.py @@ -4,7 +4,7 @@ from django.core.validators import EmailValidator from rest_framework.exceptions import ValidationError -import config +from config import config from plugins.providers import Provider @@ -20,7 +20,7 @@ def register_user(self, **kwargs): pass def validate_email(self, email): - allow_domain = config.config.get("email_allow") + allow_domain = config.get("email_allow") if allow_domain: email_validator = EmailValidator(allow_domain) else: diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index b7d36802..0b659c1d 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -8,7 +8,7 @@ from rest_framework.generics import get_object_or_404 from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST -import config +from config import config from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException from backend.mail import send_email @@ -40,12 +40,12 @@ def validate(self, data): class RegistrationSerializer(serializers.Serializer): def validate(self, _): - register_end_time = config.config.get("register_end_time") - if not (config.config.get("enable_registration") and time.time() >= config.config.get("register_start_time")) and (register_end_time < 0 or register_end_time > time.time()): + register_end_time = config.get("register_end_time") + if not (config.get("enable_registration") and time.time() >= config.get("register_start_time")) and (register_end_time < 0 or register_end_time > time.time()): raise FormattedException(m="registration_not_open", status=HTTP_403_FORBIDDEN) validated_data = providers.get_provider("registration").validate(self.initial_data) - if config.config.get("invite_required"): + if config.get("invite_required"): if not self.initial_data.get("invite", None): raise FormattedException(m="invite_required", status=HTTP_400_BAD_REQUEST) validated_data["invite"] = self.initial_data["invite"] @@ -59,7 +59,7 @@ def create(self, validated_data): user.is_superuser = True invite_code = None - if config.config.get("invite_required"): + if config.get("invite_required"): if InviteCode.objects.filter(code=validated_data["invite"]): invite_code = InviteCode.objects.get(code=validated_data["invite"]) if invite_code.uses >= invite_code.max_uses: @@ -86,7 +86,7 @@ def create(self, validated_data): if invite_code.auto_team: user.team = invite_code.auto_team - if not config.config.get("enable_teams"): + if not config.get("enable_teams"): user.save() user.team = Team.objects.create( owner=user, diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 126c9006..fd8707ec 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -6,7 +6,7 @@ from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED from rest_framework.test import APITestCase -import config +from config import config from authentication.models import PasswordResetToken, TOTPDevice, InviteCode, BackupCode, Token from authentication.views import ( VerifyEmailView, @@ -94,7 +94,7 @@ def test_register_duplicate_email(self): @mock.patch("time.time", side_effect=get_fake_time) def test_register_closed(self, mock_obj): - config.config.set("enable_prelogin", False) + config.set("enable_prelogin", False) data = { "username": "user6", "password": "uO7*$E@0ngqL", @@ -102,7 +102,7 @@ def test_register_closed(self, mock_obj): } response = self.client.post(reverse("register"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - config.config.set("enable_prelogin", True) + config.set("enable_prelogin", True) def test_register_admin(self): data = { @@ -145,14 +145,14 @@ def test_register_invalid_email(self): self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_register_teams_disabled(self): - config.config.set("enable_teams", False) + config.set("enable_teams", False) data = { "username": "user10", "password": "uO7*$E@0ngqL", "email": "user10@example.com", } response = self.client.post(reverse("register"), data) - config.config.set("enable_teams", True) + config.set("enable_teams", True) self.assertEqual(response.status_code, HTTP_201_CREATED) self.assertEqual(get_user_model().objects.get(username="user10").team.name, "user10") @@ -211,7 +211,7 @@ def test_invites_viewset(self): class InviteRequiredRegistrationTestCase(APITestCase): def setUp(self): RegistrationView.throttle_scope = "" - config.config.set("invite_required", True) + config.set("invite_required", True) InviteCode(code="test1", max_uses=10).save() InviteCode(code="test2", max_uses=1).save() InviteCode(code="test3", max_uses=1).save() @@ -225,7 +225,7 @@ def setUp(self): InviteCode(code="test4", max_uses=1, auto_team=team).save() def tearDown(self): - config.config.set("invite_required", False) + config.set("invite_required", False) def test_register_invite_required_missing_invite(self): data = { @@ -361,9 +361,9 @@ def test_login_login_closed(self, mock_obj): "username": "login-test", "password": "password", } - config.config.set("enable_prelogin", False) + config.set("enable_prelogin", False) response = self.client.post(reverse("login"), data) - config.config.set("enable_prelogin", True) + config.set("enable_prelogin", True) self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) def test_login_inactive(self): @@ -641,26 +641,26 @@ def test_password_reset_weak_password(self): self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_password_reset_login_disabled(self): - config.config.set("enable_login", False) + config.set("enable_login", False) data = { "uid": self.user.id, "token": "testtoken", "password": "uO7*$E@0ngqL", } response = self.client.post(reverse("do-password-reset"), data) - config.config.set("enable_login", True) + config.set("enable_login", True) self.assertFalse("token" in response.data["d"]) @mock.patch("time.time", side_effect=get_fake_time) def test_password_reset_cant_login_yet(self, obj): - config.config.set("enable_prelogin", False) + config.set("enable_prelogin", False) data = { "uid": self.user.id, "token": "testtoken", "password": "uO7*$E@0ngqL", } response = self.client.post(reverse("do-password-reset"), data) - config.config.set("enable_prelogin", True) + config.set("enable_prelogin", True) self.assertFalse("token" in response.data["d"]) @@ -689,14 +689,14 @@ def test_email_verify_invalid(self): self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) def test_email_verify_nologin(self): - config.config.set("enable_login", False) + config.set("enable_login", False) data = { "uid": self.user.id, "token": self.user.email_token, } response = self.client.post(reverse("verify-email"), data) - config.config.set("enable_login", False) + config.set("enable_login", False) self.assertEqual(response.data["d"], "") def test_email_verify_twice(self): diff --git a/src/backend/authentication.py b/src/backend/authentication.py index 07efb979..b4a789d7 100644 --- a/src/backend/authentication.py +++ b/src/backend/authentication.py @@ -1,7 +1,7 @@ from rest_framework import authentication from authentication.models import Token -import config +from config import config class RactfTokenAuthentication(authentication.TokenAuthentication): @@ -14,9 +14,9 @@ def authenticate(self, request): user, token = x if user.is_staff and not user.should_deny_admin(): return user, token - if config.config.get("enable_maintenance_mode"): + if config.get("enable_maintenance_mode"): return None - if not config.config.get("enable_bot_users") and user.is_bot: + if not config.get("enable_bot_users") and user.is_bot: return None if token.user_id != token.owner_id: request.sudo = True diff --git a/src/backend/permissions.py b/src/backend/permissions.py index b46d900d..a6755c82 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -2,7 +2,7 @@ from rest_framework import permissions -import config +from config import config class AdminOrReadOnlyVisible(permissions.BasePermission): @@ -28,7 +28,7 @@ def has_permission(self, request, view): class IsCompetitionOpen(permissions.BasePermission): def has_permission(self, request, view): - return config.config.get("start_time") <= time.time() + return config.get("start_time") <= time.time() class IsBot(permissions.BasePermission): diff --git a/src/challenge/models.py b/src/challenge/models.py index d2c006b1..0754164c 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -23,7 +23,7 @@ from django_prometheus.models import ExportModelOperationsMixin -import config +from config import config from plugins import plugins @@ -171,7 +171,7 @@ def get_unlocked_annotated_queryset(cls, user): Prefetch("file_set", queryset=File.objects.all(), to_attr="files"), Prefetch( "tag_set", - queryset=Tag.objects.all() if time.time() > config.config.get("end_time") else Tag.objects.filter(post_competition=False), + queryset=Tag.objects.all() if time.time() > config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags", ), "first_blood", diff --git a/src/challenge/permissions.py b/src/challenge/permissions.py index 6bb6b5ca..7b0ce977 100644 --- a/src/challenge/permissions.py +++ b/src/challenge/permissions.py @@ -2,11 +2,11 @@ from rest_framework import permissions -import config +from config import config class CompetitionOpen(permissions.BasePermission): def has_permission(self, request, view): return (request.user.is_staff and not request.user.should_deny_admin()) or ( - config.config.get("start_time") <= time.time() and (config.config.get("enable_view_challenges_after_competion") or time.time() <= config.config.get("end_time")) + config.get("start_time") <= time.time() and (config.get("enable_view_challenges_after_competion") or time.time() <= config.get("end_time")) ) diff --git a/src/challenge/sql.py b/src/challenge/sql.py index c98168f0..4853dc23 100644 --- a/src/challenge/sql.py +++ b/src/challenge/sql.py @@ -1,13 +1,13 @@ from django.core.cache import caches from django.db import connection -import config +from config import config def get_solve_counts(): cache = caches["default"] solve_counts = cache.get("solve_counts") - if solve_counts is not None and config.config.get("enable_caching"): + if solve_counts is not None and config.get("enable_caching"): return solve_counts with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=true GROUP BY challenge_id;") @@ -19,7 +19,7 @@ def get_solve_counts(): def get_incorrect_solve_counts(): cache = caches["default"] solve_counts = cache.get("incorrect_solve_counts") - if solve_counts is not None and config.config.get("enable_caching"): + if solve_counts is not None and config.get("enable_caching"): return solve_counts with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_solve WHERE correct=false GROUP BY challenge_id;") @@ -31,7 +31,7 @@ def get_incorrect_solve_counts(): def get_positive_votes(): cache = caches["default"] positive_votes = cache.get("positive_votes") - if positive_votes is not None and config.config.get("enable_caching"): + if positive_votes is not None and config.get("enable_caching"): return positive_votes with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;") @@ -43,7 +43,7 @@ def get_positive_votes(): def get_negative_votes(): cache = caches["default"] negative_votes = cache.get("negative_votes") - if negative_votes is not None and config.config.get("enable_caching"): + if negative_votes is not None and config.get("enable_caching"): return negative_votes with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;") diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 1635138c..e989c61b 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -11,7 +11,7 @@ ) from rest_framework.test import APITestCase -import config +from config import config from challenge.models import Solve from hint.models import HintUse @@ -99,9 +99,9 @@ def test_is_not_solved(self): self.assertFalse(self.challenge1.is_solved(user=self.user)) def test_submission_disabled(self): - config.config.set("enable_flag_submission", False) + config.set("enable_flag_submission", False) response = self.solve_challenge() - config.config.set("enable_flag_submission", True) + config.set("enable_flag_submission", True) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_submission_malformed(self): diff --git a/src/challenge/views.py b/src/challenge/views.py index 5d66d0c5..8fecafb7 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -39,7 +39,7 @@ FastAdminChallengeSerializer, FastAdminCategorySerializer, ) -import config +from config import config from hint.models import Hint, HintUse from team.models import Team from team.permissions import HasTeam @@ -82,7 +82,7 @@ def get_queryset(self): to_attr="hints", ), Prefetch("file_set", queryset=File.objects.all(), to_attr="files"), - Prefetch("tag_set", queryset=Tag.objects.all() if time.time() > config.config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags"), + Prefetch("tag_set", queryset=Tag.objects.all() if time.time() > config.get("end_time") else Tag.objects.filter(post_competition=False), to_attr="tags"), "hint_set__uses", ) .select_related("first_blood") @@ -98,7 +98,7 @@ def list(self, request, *args, **kwargs): cache = caches["default"] categories = cache.get(get_cache_key(request.user)) cache_hit = categories is not None - if categories is None or not config.config.get("enable_caching"): + if categories is None or not config.get("enable_caching"): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) categories = serializer.data @@ -231,7 +231,7 @@ class FlagSubmitView(APIView): throttle_scope = "flag_submit" def post(self, request): - if not config.config.get("enable_flag_submission") or (not config.config.get("enable_flag_submission_after_competition") and time.time() > config.config.get("end_time")): + if not config.get("enable_flag_submission") or (not config.get("enable_flag_submission_after_competition") and time.time() > config.get("end_time")): return FormattedResponse(m="flag_submission_disabled", status=HTTP_403_FORBIDDEN) with transaction.atomic(): @@ -282,7 +282,7 @@ class FlagCheckView(APIView): throttle_scope = "flag_submit" def post(self, request): - if not config.config.get("enable_flag_submission") or (not config.config.get("enable_flag_submission_after_competition") and time.time() > config.config.get("end_time")): + if not config.get("enable_flag_submission") or (not config.get("enable_flag_submission_after_competition") and time.time() > config.get("end_time")): return FormattedResponse(m="flag_submission_disabled", status=HTTP_403_FORBIDDEN) team = Team.objects.get(id=request.user.team.id) user = get_user_model().objects.get(id=request.user.id) diff --git a/src/config/__init__.py b/src/config/__init__.py index 0883d7ad..e69de29b 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -1,6 +0,0 @@ -from importlib import import_module - -from django.utils.functional import SimpleLazyObject - - -config = SimpleLazyObject(lambda: import_module("config.config", "config")) diff --git a/src/config/apps.py b/src/config/apps.py index 8fd75bfc..0ee32189 100644 --- a/src/config/apps.py +++ b/src/config/apps.py @@ -1,5 +1,10 @@ from django.apps import AppConfig +from config import config + class ConfigConfig(AppConfig): name = "config" + + def ready(self) -> None: + config.load() diff --git a/src/config/config.py b/src/config/config.py index a4d917f7..81ec8256 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -2,8 +2,13 @@ from django.conf import settings -backend = locate(settings.CONFIG["BACKEND"])() -backend.load(defaults=settings.DEFAULT_CONFIG) +backend = None + + +def load(): + global backend + backend = locate(settings.CONFIG["BACKEND"])() + backend.load(defaults=settings.DEFAULT_CONFIG) def get(key): @@ -28,12 +33,3 @@ def get_all_non_sensitive(): def is_sensitive(key): return key in backend.get("sensitive_fields") - - -def set_bulk(values: dict): - for key, value in values.items(): - set(key, value) - - -def add_plugin_config(name, config): - settings.DEFAULT_CONFIG[name] = config diff --git a/src/config/tests.py b/src/config/tests.py index bb938482..6e682ea9 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -3,7 +3,7 @@ from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT from rest_framework.test import APITestCase -import config +from config import config class ConfigTestCase(APITestCase): @@ -45,4 +45,4 @@ def test_update(self): self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) - self.assertEqual(config.config.get("test"), "test2") + self.assertEqual(config.get("test"), "test2") diff --git a/src/config/views.py b/src/config/views.py index 0ceacafb..52c4e94f 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -1,7 +1,7 @@ from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_201_CREATED, HTTP_204_NO_CONTENT from rest_framework.views import APIView -import config +from config import config from backend.response import FormattedResponse from backend.permissions import AdminOrAnonymousReadOnly @@ -13,23 +13,23 @@ class ConfigView(APIView): def get(self, request, name=None): if name is None: if request.user.is_superuser: - return FormattedResponse(config.config.get_all()) - return FormattedResponse(config.config.get_all_non_sensitive()) + return FormattedResponse(config.get_all()) + return FormattedResponse(config.get_all_non_sensitive()) if not config.config.is_sensitive(name) or request.is_superuser: - return FormattedResponse(config.config.get(name)) + return FormattedResponse(config.get(name)) return FormattedResponse(status=HTTP_403_FORBIDDEN) def post(self, request, name): if "value" not in request.data: return FormattedResponse(status=HTTP_400_BAD_REQUEST) - config.config.set(name, request.data.get("value")) + config.set(name, request.data.get("value")) return FormattedResponse(status=HTTP_201_CREATED) def patch(self, request, name): if "value" not in request.data: return FormattedResponse(status=HTTP_400_BAD_REQUEST) - if config.config.get(name) is not None and isinstance(config.config.get(name), list): - config.config.set("name", config.config.get(name).append(request.data["value"])) + if config.get(name) is not None and isinstance(config.get(name), list): + config.set("name", config.get(name).append(request.data["value"])) return FormattedResponse() - config.config.set(name, request.data.get("value")) + config.set(name, request.data.get("value")) return FormattedResponse(status=HTTP_204_NO_CONTENT) diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 185190c5..ec2e9265 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -4,7 +4,7 @@ from rest_framework.test import APITestCase from challenge.models import Score, Solve, Category, Challenge -import config +from config import config from leaderboard.views import UserListView, TeamListView, GraphView, CTFTimeListView from team.models import Team @@ -49,56 +49,56 @@ def setUp(self): self.user = user def test_unauthed_access(self): - config.config.set("enable_caching", False) + config.set("enable_caching", False) response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertEqual(response.status_code, HTTP_200_OK) def test_authed_access(self): self.client.force_authenticate(self.user) - config.config.set("enable_caching", False) + config.set("enable_caching", False) response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.config.set("enable_caching", False) - config.config.set("enable_scoreboard", False) + config.set("enable_caching", False) + config.set("enable_scoreboard", False) response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_scoreboard", True) - config.config.set("enable_caching", True) + config.set("enable_scoreboard", True) + config.set("enable_caching", True) self.assertEqual(response.data["d"], {}) def test_format(self): - config.config.set("enable_caching", False) + config.set("enable_caching", False) response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertTrue("user" in response.data["d"]) self.assertTrue("team" in response.data["d"]) def test_list_size(self): - config.config.set("enable_caching", False) + config.set("enable_caching", False) populate() response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertEqual(len(response.data["d"]["user"]), 10) self.assertEqual(len(response.data["d"]["team"]), 10) def test_list_sorting(self): - config.config.set("enable_caching", False) + config.set("enable_caching", False) populate() response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertEqual(response.data["d"]["user"][0]["points"], 1400) self.assertEqual(response.data["d"]["team"][0]["points"], 1400) def test_user_only(self): populate() - config.config.set("enable_teams", False) - config.config.set("enable_caching", False) + config.set("enable_teams", False) + config.set("enable_caching", False) response = self.client.get(reverse("leaderboard-graph")) - config.config.set("enable_teams", True) - config.config.set("enable_caching", True) + config.set("enable_teams", True) + config.set("enable_caching", True) self.assertEqual(len(response.data["d"]["user"]), 10) self.assertEqual(response.data["d"]["user"][0]["points"], 1400) self.assertNotIn("team", response.data["d"].keys()) @@ -121,9 +121,9 @@ def test_authed(self): self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.config.set("enable_scoreboard", False) + config.set("enable_scoreboard", False) response = self.client.get(reverse("leaderboard-user")) - config.config.set("enable_scoreboard", True) + config.set("enable_scoreboard", True) self.assertEqual(response.data["d"], {}) def test_length(self): @@ -155,9 +155,9 @@ def test_authed(self): self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.config.set("enable_scoreboard", False) + config.set("enable_scoreboard", False) response = self.client.get(reverse("leaderboard-team")) - config.config.set("enable_scoreboard", True) + config.set("enable_scoreboard", True) self.assertEqual(response.data["d"], {}) def test_length(self): @@ -189,15 +189,15 @@ def test_authed(self): self.assertEqual(response.status_code, HTTP_200_OK) def test_disabled_access(self): - config.config.set("enable_scoreboard", False) + config.set("enable_scoreboard", False) response = self.client.get(reverse("leaderboard-ctftime")) - config.config.set("enable_scoreboard", True) + config.set("enable_scoreboard", True) self.assertEqual(response.data, {}) def test_disabled_ctftime(self): - config.config.set("enable_ctftime", False) + config.set("enable_ctftime", False) response = self.client.get(reverse("leaderboard-ctftime")) - config.config.set("enable_ctftime", True) + config.set("enable_ctftime", True) self.assertEqual(response.data, {}) def test_length(self): @@ -251,7 +251,7 @@ def test_order(self): self.assertEqual(points, sorted(points, reverse=True)) def test_disabled_scoreboard(self): - config.config.set("enable_scoreboard", False) + config.set("enable_scoreboard", False) response = self.client.get(reverse("leaderboard-matrix-list")) - config.config.set("enable_scoreboard", True) + config.set("enable_scoreboard", True) self.assertEqual(response.data["d"], {}) diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index d42aba09..e02f6302 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -10,14 +10,14 @@ from backend.response import FormattedResponse from challenge.models import Score -import config +from config import config from leaderboard.serializers import LeaderboardUserScoreSerializer, LeaderboardTeamScoreSerializer, UserPointsSerializer, TeamPointsSerializer, CTFTimeSerializer, MatrixSerializer from team.models import Team def should_hide_scoreboard(): - return not config.config.get("enable_scoreboard") and ( - config.config.get("hide_scoreboard_at") == -1 or config.config.get("hide_scoreboard_at") > time.time() or config.config.get("end_time") > time.time() + return not config.get("enable_scoreboard") and ( + config.get("hide_scoreboard_at") == -1 or config.get("hide_scoreboard_at") > time.time() or config.get("end_time") > time.time() ) @@ -28,7 +28,7 @@ class CTFTimeListView(APIView): ) def get(self, request, *args, **kwargs): - if should_hide_scoreboard() or not config.config.get("enable_ctftime"): + if should_hide_scoreboard() or not config.get("enable_ctftime"): return Response({}) teams = Team.objects.visible().ranked() return Response({"standings": CTFTimeSerializer(teams, many=True).data}) @@ -43,10 +43,10 @@ def get(self, request, *args, **kwargs): cache = caches["default"] cached_leaderboard = cache.get("leaderboard_graph") - if cached_leaderboard is not None and config.config.get("enable_caching"): + if cached_leaderboard is not None and config.get("enable_caching"): return FormattedResponse(cached_leaderboard) - graph_members = config.config.get("graph_members") + graph_members = config.get("graph_members") top_teams = Team.objects.visible().ranked()[:graph_members] top_users = get_user_model().objects.filter(is_visible=True).order_by("-leaderboard_points", "last_score")[:graph_members] @@ -56,7 +56,7 @@ def get(self, request, *args, **kwargs): user_serializer = LeaderboardUserScoreSerializer(user_scores, many=True) team_serializer = LeaderboardTeamScoreSerializer(team_scores, many=True) response = {"user": user_serializer.data} - if config.config.get("enable_teams"): + if config.get("enable_teams"): response["team"] = team_serializer.data cache.set("leaderboard_graph", response, 15) diff --git a/src/member/models.py b/src/member/models.py index 490b67d2..c7d19e5f 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -12,7 +12,7 @@ from django_prometheus.models import ExportModelOperationsMixin -import config +from config import config from backend.validators import printable_name diff --git a/src/member/serializers.py b/src/member/serializers.py index 47444cc2..4c1bd43c 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -6,7 +6,7 @@ from backend.mixins import IncorrectSolvesMixin from challenge.serializers import SolveSerializer from member.models import UserIP -import config +from config import config class MemberSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): @@ -146,7 +146,7 @@ def validate_email(self, value): return value def update(self, instance, validated_data): - if not config.config.get("enable_teams"): + if not config.get("enable_teams"): if instance.team: instance.team.name = validated_data.get("username", instance.username) return super(SelfSerializer, self).update(instance, validated_data) diff --git a/src/plugins/flag/lenient.py b/src/plugins/flag/lenient.py index 83f17c4d..930e5174 100644 --- a/src/plugins/flag/lenient.py +++ b/src/plugins/flag/lenient.py @@ -1,4 +1,4 @@ -import config +from config import config from plugins.flag.base import FlagPlugin import unicodedata @@ -16,7 +16,7 @@ def strip_whitespace(s): def fix_format(s): - prefix = config.config.get("flag_prefix") + prefix = config.get("flag_prefix") return s if prefix + "{" in s else prefix + "{" + s + "}" diff --git a/src/plugins/points/base.py b/src/plugins/points/base.py index 9cbee06d..9072b7e1 100644 --- a/src/plugins/points/base.py +++ b/src/plugins/points/base.py @@ -4,7 +4,7 @@ from django.db.models import Sum, F from django.utils import timezone -import config +from config import config from challenge.models import Score, Solve from hint.models import HintUse from plugins.base import Plugin @@ -33,7 +33,7 @@ def score(self, user, team, flag, solves, *args, **kwargs): deducted = HintUse.objects.filter(user=user, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) deducted = 0 if deducted["points"] is None else deducted["points"] deducted = min(points, deducted) - scored = config.config.get("end_time") >= time.time() and config.config.get("enable_scoring") + scored = config.get("end_time") >= time.time() and config.get("enable_scoring") score = Score(team=team, reason="challenge", points=points, penalty=deducted, leaderboard=scored, user=user) score.save() solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, flag=flag, score=score) @@ -50,5 +50,5 @@ def score(self, user, team, flag, solves, *args, **kwargs): return solve def register_incorrect_attempt(self, user, team, flag, solves, *args, **kwargs): - if config.config.get("enable_track_incorrect_submissions"): + if config.get("enable_track_incorrect_submissions"): Solve(team=team, solved_by=user, challenge=self.challenge, flag=flag, correct=False, score=None).save() diff --git a/src/plugins/providers.py b/src/plugins/providers.py index ce225027..c33f67e1 100644 --- a/src/plugins/providers.py +++ b/src/plugins/providers.py @@ -1,7 +1,7 @@ import abc from collections import defaultdict -import config +from config import config providers = defaultdict(dict) @@ -11,7 +11,7 @@ def register_provider(provider_type, provider): def get_provider(provider_type): - return providers[provider_type][config.config.get(provider_type + "_provider")] + return providers[provider_type][config.get(provider_type + "_provider")] class Provider(abc.ABC): diff --git a/src/plugins/tests.py b/src/plugins/tests.py index c1c44691..671075ef 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -3,7 +3,7 @@ from challenge.models import Category, Challenge, Solve, Score from challenge.tests.mixins import ChallengeSetupMixin -import config +from config import config from plugins import plugins from plugins.flag.hashed import HashedFlagPlugin from plugins.flag.lenient import LenientFlagPlugin @@ -212,7 +212,7 @@ def test_score(self): self.assertEqual(self.team.leaderboard_points, 1000) def test_score_lb_disabled(self): - config.config.set("enable_scoring", False) + config.set("enable_scoring", False) self.plugin.score(self.user, self.team, "", Solve.objects.filter(challenge=self.challenge2)) self.assertEqual(self.team.points, 1000) self.assertEqual(self.team.leaderboard_points, 0) diff --git a/src/ractf/management/commands/copy_points.py b/src/ractf/management/commands/copy_points.py index dcb00736..025df69e 100644 --- a/src/ractf/management/commands/copy_points.py +++ b/src/ractf/management/commands/copy_points.py @@ -3,14 +3,14 @@ from django.core.management import BaseCommand from challenge.models import Score -import config +from config import config class Command(BaseCommand): help = "Removes all scores from the database" def handle(self, *args, **options): - if time.time() > config.config.get("end_time"): + if time.time() > config.get("end_time"): return for score in Score.objects.all(): if not score.leaderboard: diff --git a/src/sockets/signals.py b/src/sockets/signals.py index 82402b96..af16a1a3 100644 --- a/src/sockets/signals.py +++ b/src/sockets/signals.py @@ -3,7 +3,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver -import config +from config import config from announcements.models import Announcement from announcements.serializers import AnnouncementSerializer from backend.signals import flag_score, flag_reject, use_hint, team_join @@ -26,7 +26,7 @@ def broadcast(data): @receiver(flag_score) def on_flag_score(user, team, challenge, flag, solve, **kwargs): - if not config.config.get("enable_solve_broadcast"): + if not config.get("enable_solve_broadcast"): return broadcast( { diff --git a/src/stats/tests.py b/src/stats/tests.py index 2396ce34..30226c16 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -3,7 +3,7 @@ from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED from rest_framework.test import APITestCase -import config +from config import config from challenge.models import Challenge, Category, Solve from team.models import Team @@ -103,9 +103,9 @@ def test_challenge_data(self): Solve.objects.create(challenge=chall, flag="", correct=False) self.client.force_authenticate(user) - config.config.set("enable_caching", False) + config.set("enable_caching", False) response = self.client.get(reverse("full")) - config.config.set("enable_caching", True) + config.set("enable_caching", True) self.assertEqual(response.data["d"]["challenges"][chall.id]["incorrect"], 1) self.assertEqual(response.data["d"]["challenges"][chall.id]["correct"], 1) diff --git a/src/stats/views.py b/src/stats/views.py index 62169bec..e97fed4d 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -9,7 +9,7 @@ from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView -import config +from config import config from backend.response import FormattedResponse from challenge.models import Score from challenge.sql import get_incorrect_solve_counts, get_solve_counts @@ -22,9 +22,9 @@ def countdown(request): return FormattedResponse( { - "countdown_timestamp": config.config.get("start_time"), - "registration_open": config.config.get("register_start_time"), - "competition_end": config.config.get("end_time"), + "countdown_timestamp": config.get("start_time"), + "registration_open": config.get("register_start_time"), + "competition_end": config.get("end_time"), "server_timestamp": datetime.now(timezone.utc).isoformat(), } ) diff --git a/src/team/permissions.py b/src/team/permissions.py index cbf40281..892c03cc 100644 --- a/src/team/permissions.py +++ b/src/team/permissions.py @@ -1,6 +1,6 @@ from rest_framework import permissions -import config +from config import config class IsTeamOwnerOrReadOnly(permissions.BasePermission): @@ -15,4 +15,4 @@ def has_permission(self, request, view): class TeamsEnabled(permissions.BasePermission): def has_permission(self, request, view): - return config.config.get("enable_teams") + return config.get("enable_teams") diff --git a/src/team/tests.py b/src/team/tests.py index 64a6cde9..2e9daeba 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -10,7 +10,7 @@ ) from rest_framework.test import APITestCase -import config +from config import config from challenge.models import Solve, Category, Challenge from team.models import Team @@ -65,13 +65,13 @@ def test_update_not_owner(self): def test_team_leave_disabled(self): self.client.force_authenticate(user=self.user) - config.config.set("enable_team_leave", False) + config.set("enable_team_leave", False) response = self.client.post(reverse("team-leave")) - config.config.set("enable_team_leave", True) + config.set("enable_team_leave", True) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_challenge_solved(self): - config.config.set("enable_team_leave", True) + config.set("enable_team_leave", True) self.client.force_authenticate(user=self.user) category = Category.objects.create(name="test category", display_order=1, contained_type="test", description="test") @@ -89,7 +89,7 @@ def test_team_leave_challenge_solved(self): Solve.objects.create(solved_by=self.user, flag="", challenge=chall) response = self.client.post(reverse("team-leave")) - config.config.set("enable_team_leave", False) + config.set("enable_team_leave", False) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_with_members(self): @@ -99,17 +99,17 @@ def test_team_leave_as_owner_with_members(self): self.admin_user.is_staff = False self.admin_user.save() - config.config.set("enable_team_leave", True) + config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) - config.config.set("enable_team_leave", False) + config.set("enable_team_leave", False) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_team_leave_as_owner_without_members(self): self.client.force_authenticate(user=self.user) - config.config.set("enable_team_leave", True) + config.set("enable_team_leave", True) response = self.client.post(reverse("team-leave")) - config.config.set("enable_team_leave", False) + config.set("enable_team_leave", False) self.assertEqual(response.status_code, HTTP_200_OK) @@ -154,7 +154,7 @@ def test_join_team_full(self): user2 = get_user_model()(username="team-test2", email="team-test2@example.org", is_visible=True) user2.save() self.client.force_authenticate(self.admin_user) - config.config.set("team_size", 1) + config.set("team_size", 1) self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) self.client.force_authenticate(user2) response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) @@ -162,9 +162,9 @@ def test_join_team_full(self): def test_join_team_disabled(self): self.client.force_authenticate(self.admin_user) - config.config.set("enable_team_join", False) + config.set("enable_team_join", False) response = self.client.post(reverse("team-join"), data={"name": "team-test", "password": "abc"}) - config.config.set("enable_team_join", True) + config.set("enable_team_join", True) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_join_team_duplicate(self): diff --git a/src/team/views.py b/src/team/views.py index d6f1e04c..8c1adf57 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -9,7 +9,7 @@ from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND from rest_framework.views import APIView -import config +from config import config from backend.exceptions import FormattedException from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.response import FormattedResponse @@ -101,7 +101,7 @@ class JoinTeamView(APIView): throttle_scope = "team_join" def post(self, request): - if not config.config.get("enable_team_join"): + if not config.get("enable_team_join"): return FormattedResponse(m="join_disabled", status=HTTP_403_FORBIDDEN) name = request.data.get("name") password = request.data.get("password") @@ -115,7 +115,7 @@ def post(self, request): except Http404: team_join_reject.send(sender=self.__class__, user=request.user, name=name) raise FormattedException(m="invalid_team", status=HTTP_404_NOT_FOUND) - team_size = int(config.config.get("team_size")) + team_size = int(config.get("team_size")) if not request.user.is_staff and not team.size_limit_exempt and 0 < team_size <= team.members.count(): return FormattedResponse(m="team_full", status=HTTP_403_FORBIDDEN) request.user.team = team @@ -133,7 +133,7 @@ class LeaveTeamView(APIView): permission_classes = (IsAuthenticated & HasTeam & TeamsEnabled,) def post(self, request): - if not config.config.get("enable_team_leave"): + if not config.get("enable_team_leave"): return FormattedResponse(m="leave_disabled", status=HTTP_403_FORBIDDEN) if Solve.objects.filter(solved_by=request.user).exists(): return FormattedResponse(m="challenge_solved", status=HTTP_403_FORBIDDEN) From 3052d268a550a2ea97872bfd4e98bc3f26c7e4bf Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Jun 2021 17:36:10 +0100 Subject: [PATCH 063/185] refactor config --- src/config/config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index 81ec8256..ffae569e 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -1,13 +1,12 @@ from pydoc import locate from django.conf import settings +from django.utils.functional import SimpleLazyObject -backend = None +backend = SimpleLazyObject(lambda: locate(settings.CONFIG["BACKEND"])()) def load(): - global backend - backend = locate(settings.CONFIG["BACKEND"])() backend.load(defaults=settings.DEFAULT_CONFIG) From 735c6478a3df94966b6a725074255294a07906ce Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:23:49 +0100 Subject: [PATCH 064/185] Add isort to dev dependencies --- poetry.lock | 19 ++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 4f68fa53..82f035bf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -666,6 +666,19 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + [[package]] name = "itypes" version = "1.2.0" @@ -1403,7 +1416,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "97149bd25ead908f42e3b0746abb0a69933099976c21265b33ef37e5b3594521" +content-hash = "867fb1b06fa9888e6ac9c929a18da80e5b600c88a7b01af79be2f74c9a9b0d45" [metadata.files] aioredis = [ @@ -1765,6 +1778,10 @@ ipython-genutils = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, ] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] itypes = [ {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, diff --git a/pyproject.toml b/pyproject.toml index 4e586574..22f74cd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ autoflake = "^1.4" pytest = "^6.2.4" pytest-cov = "^2.12.0" pytest-django = "^4.3.0" +isort = "^5.8.0" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "backend.settings.local" From 5f5c2c97c96c50ae435bf4807da95591340a126d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:24:01 +0100 Subject: [PATCH 065/185] Run isort to remove unused imports --- src/admin/tests.py | 2 +- src/admin/views.py | 1 - src/andromeda/views.py | 6 ++-- src/announcements/migrations/0001_initial.py | 2 +- src/announcements/models.py | 1 - src/announcements/urls.py | 2 +- src/authentication/basic_auth.py | 10 +++--- .../migrations/0002_invitecode_auto_team.py | 2 +- .../migrations/0003_passwordresettoken.py | 7 ++-- .../migrations/0004_auto_20200810_1111.py | 4 +-- .../migrations/0005_auto_20200813_2210.py | 2 +- .../migrations/0006_auto_20200813_2217.py | 2 +- src/authentication/migrations/0007_token.py | 2 +- .../migrations/0008_auto_20200816_1827.py | 2 +- src/authentication/models.py | 1 - src/authentication/serializers.py | 7 ++-- src/authentication/tests.py | 26 ++++++-------- src/authentication/urls.py | 2 +- src/authentication/views.py | 31 ++++++++-------- src/backend/backends.py | 1 - src/backend/exception_handler.py | 6 ++-- src/backend/mail.py | 3 +- src/backend/settings/__init__.py | 1 - src/backend/settings/local.py | 1 - src/backend/settings/production.py | 3 +- src/backend/settings/staging.py | 3 +- src/backend/settings/test.py | 1 - src/backend/throttling.py | 2 +- src/backend/urls.py | 3 +- src/backend/validators.py | 1 - src/challenge/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20200808_1337.py | 4 +-- .../migrations/0003_challengevote.py | 2 +- .../migrations/0005_challengefeedback.py | 2 +- src/challenge/migrations/0006_tag.py | 2 +- src/challenge/migrations/0007_file_upload.py | 3 +- .../migrations/0008_auto_20201223_1623.py | 3 +- .../migrations/0010_auto_20201225_2135.py | 2 +- .../migrations/0014_migrate_legacy_unlocks.py | 1 + src/challenge/models.py | 17 ++------- src/challenge/serializers.py | 6 ++-- src/challenge/tests/test_views.py | 16 +++------ src/challenge/urls.py | 2 +- src/challenge/views.py | 36 +++++++++---------- src/config/models.py | 1 - src/config/tests.py | 3 +- src/config/views.py | 7 ++-- src/hint/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20200808_1337.py | 2 +- src/hint/models.py | 1 - src/hint/tests.py | 3 +- src/hint/urls.py | 2 +- src/hint/views.py | 10 ++---- src/leaderboard/tests.py | 5 +-- src/leaderboard/urls.py | 2 +- src/leaderboard/views.py | 8 +++-- src/member/migrations/0001_initial.py | 12 ++++--- .../migrations/0002_auto_20200808_1337.py | 2 +- .../migrations/0003_auto_20200808_1649.py | 3 +- src/member/models.py | 3 +- src/member/serializers.py | 2 +- src/member/tests.py | 8 ++--- src/member/urls.py | 2 +- src/member/views.py | 6 ++-- src/pages/models.py | 1 - src/pages/serializers.py | 1 + src/pages/urls.py | 2 +- src/pages/views.py | 2 +- src/plugins/flag/lenient.py | 3 +- src/plugins/flag/map.py | 4 +-- src/plugins/models.py | 1 + src/plugins/plugins.py | 2 +- src/plugins/points/base.py | 4 +-- src/plugins/tests.py | 2 +- src/polaris/urls.py | 2 +- src/polaris/views.py | 2 +- src/ractf/apps.py | 2 +- src/ractf/management/commands/flush_db.py | 3 +- src/ractf/management/commands/getschema.py | 4 +-- .../management/commands/insert_dummy_data.py | 2 +- .../commands/insert_mega_dummy_data.py | 2 +- src/ractf/management/commands/reset_scores.py | 2 +- src/scorerecalculator/tests.py | 3 +- src/sockets/consumers.py | 2 +- src/sockets/signals.py | 4 +-- src/stats/apps.py | 4 ++- src/stats/signals.py | 6 ++-- src/stats/tests.py | 5 +-- src/stats/views.py | 7 ++-- src/team/migrations/0001_initial.py | 7 ++-- .../migrations/0002_auto_20200808_1649.py | 3 +- src/team/models.py | 1 - src/team/serializers.py | 2 +- src/team/tests.py | 13 +++---- src/team/urls.py | 2 +- src/team/views.py | 28 ++++++--------- 96 files changed, 208 insertions(+), 239 deletions(-) diff --git a/src/admin/tests.py b/src/admin/tests.py index 6bddbf4e..7b12c0c1 100644 --- a/src/admin/tests.py +++ b/src/admin/tests.py @@ -3,7 +3,7 @@ from django.urls import reverse from rest_framework.test import APITestCase -from challenge.models import Challenge, Category +from challenge.models import Category, Challenge from member.models import Member diff --git a/src/admin/views.py b/src/admin/views.py index 368274eb..dad575d0 100644 --- a/src/admin/views.py +++ b/src/admin/views.py @@ -1,5 +1,4 @@ from django.shortcuts import render - # Create your views here. from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView diff --git a/src/andromeda/views.py b/src/andromeda/views.py index 48b748ac..fff99f3f 100644 --- a/src/andromeda/views.py +++ b/src/andromeda/views.py @@ -1,11 +1,11 @@ from rest_framework.generics import get_object_or_404 -from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.views import APIView -from backend.response import FormattedResponse -from challenge.models import Challenge from andromeda import client from andromeda.serializers import JobSubmitSerializer +from backend.response import FormattedResponse +from challenge.models import Challenge class GetInstanceView(APIView): diff --git a/src/announcements/migrations/0001_initial.py b/src/announcements/migrations/0001_initial.py index 3fdc98df..7ece6ba4 100644 --- a/src/announcements/migrations/0001_initial.py +++ b/src/announcements/migrations/0001_initial.py @@ -1,7 +1,7 @@ # Generated by Django 3.0.2 on 2020-04-28 20:42 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/announcements/models.py b/src/announcements/models.py index 585cbdd3..0808b631 100644 --- a/src/announcements/models.py +++ b/src/announcements/models.py @@ -1,6 +1,5 @@ from django.db import models from django.utils import timezone - from django_prometheus.models import ExportModelOperationsMixin diff --git a/src/announcements/urls.py b/src/announcements/urls.py index 19c3761d..f8739a64 100644 --- a/src/announcements/urls.py +++ b/src/announcements/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from announcements import views diff --git a/src/authentication/basic_auth.py b/src/authentication/basic_auth.py index 04126c65..26dc8428 100644 --- a/src/authentication/basic_auth.py +++ b/src/authentication/basic_auth.py @@ -1,10 +1,12 @@ -from django.contrib.auth import authenticate, get_user_model, password_validation -from rest_framework.status import HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST +from django.contrib.auth import (authenticate, get_user_model, + password_validation) from rest_framework.exceptions import ValidationError +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED -from authentication.providers import LoginProvider, TokenProvider, RegistrationProvider +from authentication.providers import (LoginProvider, RegistrationProvider, + TokenProvider) from backend.exceptions import FormattedException -from backend.signals import login_reject, login +from backend.signals import login, login_reject class BasicAuthRegistrationProvider(RegistrationProvider): diff --git a/src/authentication/migrations/0002_invitecode_auto_team.py b/src/authentication/migrations/0002_invitecode_auto_team.py index b49bb800..a0f2e2bd 100644 --- a/src/authentication/migrations/0002_invitecode_auto_team.py +++ b/src/authentication/migrations/0002_invitecode_auto_team.py @@ -1,7 +1,7 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0003_passwordresettoken.py b/src/authentication/migrations/0003_passwordresettoken.py index bebc876a..f1f14c71 100644 --- a/src/authentication/migrations/0003_passwordresettoken.py +++ b/src/authentication/migrations/0003_passwordresettoken.py @@ -1,10 +1,11 @@ # Generated by Django 3.0.5 on 2020-08-08 19:51 -import authentication.models -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + +import authentication.models class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0004_auto_20200810_1111.py b/src/authentication/migrations/0004_auto_20200810_1111.py index c661886c..205d2980 100644 --- a/src/authentication/migrations/0004_auto_20200810_1111.py +++ b/src/authentication/migrations/0004_auto_20200810_1111.py @@ -1,9 +1,9 @@ # Generated by Django 3.0.5 on 2020-08-10 11:11 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import pyotp +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0005_auto_20200813_2210.py b/src/authentication/migrations/0005_auto_20200813_2210.py index 221817ff..eb5fe07b 100644 --- a/src/authentication/migrations/0005_auto_20200813_2210.py +++ b/src/authentication/migrations/0005_auto_20200813_2210.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-13 22:10 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0006_auto_20200813_2217.py b/src/authentication/migrations/0006_auto_20200813_2217.py index ecfd7083..a479b70f 100644 --- a/src/authentication/migrations/0006_auto_20200813_2217.py +++ b/src/authentication/migrations/0006_auto_20200813_2217.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-13 22:17 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0007_token.py b/src/authentication/migrations/0007_token.py index 7aa1654f..47e7f662 100644 --- a/src/authentication/migrations/0007_token.py +++ b/src/authentication/migrations/0007_token.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-16 01:27 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/authentication/migrations/0008_auto_20200816_1827.py b/src/authentication/migrations/0008_auto_20200816_1827.py index 69b09dc8..ea0a0224 100644 --- a/src/authentication/migrations/0008_auto_20200816_1827.py +++ b/src/authentication/migrations/0008_auto_20200816_1827.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-16 18:27 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/authentication/models.py b/src/authentication/models.py index fb1c5e98..78f47917 100644 --- a/src/authentication/models.py +++ b/src/authentication/models.py @@ -6,7 +6,6 @@ from django.conf import settings from django.db import models from django.utils import timezone - from django_prometheus.models import ExportModelOperationsMixin from team.models import Team diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 0b659c1d..61e105a9 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -1,18 +1,19 @@ -import time import secrets +import time from django.conf import settings from django.contrib.auth import get_user_model, password_validation from django.utils import timezone from rest_framework import serializers from rest_framework.generics import get_object_or_404 -from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST +from rest_framework.status import (HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN) -from config import config from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException from backend.mail import send_email from backend.signals import register +from config import config from plugins import providers from team.models import Team diff --git a/src/authentication/tests.py b/src/authentication/tests.py index fd8707ec..fed564a1 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -3,24 +3,20 @@ import pyotp from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED +from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND) from rest_framework.test import APITestCase +from authentication.models import (BackupCode, InviteCode, PasswordResetToken, + Token, TOTPDevice) +from authentication.views import (AddTwoFactorView, ChangePasswordView, + CreateBotView, DoPasswordResetView, + LoginTwoFactorView, LoginView, + RegenerateBackupCodesView, RegistrationView, + RequestPasswordResetView, VerifyEmailView, + VerifyTwoFactorView) from config import config -from authentication.models import PasswordResetToken, TOTPDevice, InviteCode, BackupCode, Token -from authentication.views import ( - VerifyEmailView, - DoPasswordResetView, - AddTwoFactorView, - VerifyTwoFactorView, - LoginView, - RegistrationView, - ChangePasswordView, - LoginTwoFactorView, - RequestPasswordResetView, - RegenerateBackupCodesView, - CreateBotView, -) from team.models import Team diff --git a/src/authentication/urls.py b/src/authentication/urls.py index 28320c79..c06092a8 100644 --- a/src/authentication/urls.py +++ b/src/authentication/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from authentication import views diff --git a/src/authentication/views.py b/src/authentication/views.py index ffcfe32c..0de8f1bd 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -2,34 +2,37 @@ import secrets import string +from django.conf import settings from django.contrib.auth import get_user_model from django.core.validators import EmailValidator -from django.conf import settings from django.db import transaction from django.utils.decorators import method_decorator from django.views.decorators.debug import sensitive_post_parameters from django_filters.rest_framework import DjangoFilterBackend from rest_framework import permissions -from rest_framework.generics import CreateAPIView, GenericAPIView, get_object_or_404 -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED +from rest_framework.generics import (CreateAPIView, GenericAPIView, + get_object_or_404) +from rest_framework.status import (HTTP_201_CREATED, HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED) from rest_framework.views import APIView from authentication import serializers -from authentication.models import InviteCode, PasswordResetToken, TOTPDevice, BackupCode +from authentication.models import (BackupCode, InviteCode, PasswordResetToken, + TOTPDevice) from authentication.permissions import HasTwoFactor, VerifyingTwoFactor -from authentication.serializers import ( - RegistrationSerializer, - EmailVerificationSerializer, - ChangePasswordSerializer, - GenerateInvitesSerializer, - InviteCodeSerializer, - EmailSerializer, - CreateBotSerializer, -) +from authentication.serializers import (ChangePasswordSerializer, + CreateBotSerializer, EmailSerializer, + EmailVerificationSerializer, + GenerateInvitesSerializer, + InviteCodeSerializer, + RegistrationSerializer) from backend.mail import send_email from backend.permissions import IsBot, IsSudo from backend.response import FormattedResponse -from backend.signals import logout, add_2fa, verify_2fa, password_reset_start, password_reset_start_reject, email_verified, change_password, password_reset, remove_2fa +from backend.signals import (add_2fa, change_password, email_verified, logout, + password_reset, password_reset_start, + password_reset_start_reject, remove_2fa, + verify_2fa) from backend.viewsets import AdminListModelViewSet from plugins import providers from team.models import Team diff --git a/src/backend/backends.py b/src/backend/backends.py index 774c2c3d..ad7ca93c 100644 --- a/src/backend/backends.py +++ b/src/backend/backends.py @@ -1,7 +1,6 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend - UserModel = get_user_model() diff --git a/src/backend/exception_handler.py b/src/backend/exception_handler.py index bf73931d..c17dd0fb 100644 --- a/src/backend/exception_handler.py +++ b/src/backend/exception_handler.py @@ -1,17 +1,15 @@ import traceback from typing import Optional +import sentry_sdk +from django.conf import settings from django.http import Http404 - from rest_framework import exceptions from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR from rest_framework.views import exception_handler -import sentry_sdk - -from django.conf import settings from backend.exceptions import FormattedException from backend.response import FormattedResponse diff --git a/src/backend/mail.py b/src/backend/mail.py index 0aaec38a..f9f59d44 100644 --- a/src/backend/mail.py +++ b/src/backend/mail.py @@ -1,8 +1,7 @@ from importlib import import_module -from django.template.loader import render_to_string from django.conf import settings - +from django.template.loader import render_to_string if settings.MAIL["SEND"]: # pragma: no cover if settings.MAIL["SEND_MODE"] == "AWS": # pragma: no cover diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index 827540da..eab45265 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -8,7 +8,6 @@ from corsheaders.defaults import default_headers - CORS_ALLOW_HEADERS = [*default_headers, "x-exporting", "exporting"] DOMAIN = os.getenv("DOMAIN") diff --git a/src/backend/settings/local.py b/src/backend/settings/local.py index 306858dc..2af7a896 100644 --- a/src/backend/settings/local.py +++ b/src/backend/settings/local.py @@ -1,5 +1,4 @@ from . import * - DOMAIN = "localhost" MAIL["SEND"] = False diff --git a/src/backend/settings/production.py b/src/backend/settings/production.py index abd140d6..9bf4fda7 100644 --- a/src/backend/settings/production.py +++ b/src/backend/settings/production.py @@ -2,12 +2,11 @@ # flake8: noqa -from . import * - import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.redis import RedisIntegration +from . import * SEND_MAIL = True diff --git a/src/backend/settings/staging.py b/src/backend/settings/staging.py index 055f5483..6633ed26 100644 --- a/src/backend/settings/staging.py +++ b/src/backend/settings/staging.py @@ -2,11 +2,10 @@ # flake8: noqa -from . import * - import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration +from . import * sentry_sdk.init(dsn="https://965545cacdd14caca2d2a037af90e7a7@o104250.ingest.sentry.io/5374672", integrations=[DjangoIntegration()], send_default_pii=True) diff --git a/src/backend/settings/test.py b/src/backend/settings/test.py index 16e80529..f086238a 100644 --- a/src/backend/settings/test.py +++ b/src/backend/settings/test.py @@ -1,6 +1,5 @@ from . import * - SECRET_KEY = "CorrectHorseBatteryStaple" MAIL["SEND"] = False diff --git a/src/backend/throttling.py b/src/backend/throttling.py index c6d38742..87ebaf73 100644 --- a/src/backend/throttling.py +++ b/src/backend/throttling.py @@ -1,5 +1,5 @@ -from rest_framework import throttling from django.conf import settings +from rest_framework import throttling class AdminBypassThrottle(throttling.ScopedRateThrottle): diff --git a/src/backend/urls.py b/src/backend/urls.py index 7c1c61b9..ebf0f267 100644 --- a/src/backend/urls.py +++ b/src/backend/urls.py @@ -13,10 +13,9 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.urls import path, include - from django.conf import settings from django.conf.urls.static import static +from django.urls import include, path from backend.views import CatchAllView diff --git a/src/backend/validators.py b/src/backend/validators.py index 203390d3..4ed7eae3 100644 --- a/src/backend/validators.py +++ b/src/backend/validators.py @@ -5,7 +5,6 @@ from django.utils.deconstruct import deconstructible from django.utils.translation import gettext_lazy as _ - PRINTABLE_CHARS = set(printable.strip() + " ") diff --git a/src/challenge/migrations/0001_initial.py b/src/challenge/migrations/0001_initial.py index 94242f49..6eac3417 100644 --- a/src/challenge/migrations/0001_initial.py +++ b/src/challenge/migrations/0001_initial.py @@ -1,9 +1,9 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 import django.contrib.postgres.fields.jsonb -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0002_auto_20200808_1337.py b/src/challenge/migrations/0002_auto_20200808_1337.py index d77b9970..3829d692 100644 --- a/src/challenge/migrations/0002_auto_20200808_1337.py +++ b/src/challenge/migrations/0002_auto_20200808_1337.py @@ -2,10 +2,10 @@ import os -from django.conf import settings import django.contrib.postgres.indexes -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0003_challengevote.py b/src/challenge/migrations/0003_challengevote.py index 3ffd5319..2435df40 100644 --- a/src/challenge/migrations/0003_challengevote.py +++ b/src/challenge/migrations/0003_challengevote.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-08 15:49 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0005_challengefeedback.py b/src/challenge/migrations/0005_challengefeedback.py index d6be8730..1a6f15cc 100644 --- a/src/challenge/migrations/0005_challengefeedback.py +++ b/src/challenge/migrations/0005_challengefeedback.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-08 20:13 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0006_tag.py b/src/challenge/migrations/0006_tag.py index 599c1265..9f34fc7d 100644 --- a/src/challenge/migrations/0006_tag.py +++ b/src/challenge/migrations/0006_tag.py @@ -1,7 +1,7 @@ # Generated by Django 3.0.5 on 2020-08-16 00:43 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0007_file_upload.py b/src/challenge/migrations/0007_file_upload.py index 8384f264..fecbcc52 100644 --- a/src/challenge/migrations/0007_file_upload.py +++ b/src/challenge/migrations/0007_file_upload.py @@ -1,8 +1,9 @@ # Generated by Django 3.1.4 on 2020-12-23 16:22 -import challenge.models from django.db import migrations, models +import challenge.models + class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0008_auto_20201223_1623.py b/src/challenge/migrations/0008_auto_20201223_1623.py index 7a369aea..473fa3f6 100644 --- a/src/challenge/migrations/0008_auto_20201223_1623.py +++ b/src/challenge/migrations/0008_auto_20201223_1623.py @@ -1,8 +1,9 @@ # Generated by Django 3.1.4 on 2020-12-23 16:23 -import challenge.models from django.db import migrations, models +import challenge.models + class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0010_auto_20201225_2135.py b/src/challenge/migrations/0010_auto_20201225_2135.py index 633ce8c0..e3e04815 100644 --- a/src/challenge/migrations/0010_auto_20201225_2135.py +++ b/src/challenge/migrations/0010_auto_20201225_2135.py @@ -1,7 +1,7 @@ # Generated by Django 3.1 on 2020-12-25 21:35 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/challenge/migrations/0014_migrate_legacy_unlocks.py b/src/challenge/migrations/0014_migrate_legacy_unlocks.py index c60d325d..c8f60051 100644 --- a/src/challenge/migrations/0014_migrate_legacy_unlocks.py +++ b/src/challenge/migrations/0014_migrate_legacy_unlocks.py @@ -2,6 +2,7 @@ from django.db import migrations + def migrate_unlocks(apps, schema_editor): Challenge = apps.get_model("challenge", "Challenge") for chall in Challenge.objects.all(): diff --git a/src/challenge/models.py b/src/challenge/models.py index 0754164c..b8b9e47b 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -3,24 +3,14 @@ from django.contrib.auth import get_user_model from django.contrib.postgres.indexes import BrinIndex from django.db import models -from django.db.models import ( - SET_NULL, - CASCADE, - PROTECT, - Case, - When, - Value, - UniqueConstraint, - Q, - JSONField, -) +from django.db.models import (CASCADE, PROTECT, SET_NULL, Case, JSONField, Q, + UniqueConstraint, Value, When) from django.db.models.aggregates import Count from django.db.models.query import Prefetch from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone from django.utils.functional import cached_property - from django_prometheus.models import ExportModelOperationsMixin from config import config @@ -150,8 +140,7 @@ def get_unlocked_annotated_queryset(cls, user): output_field=models.BooleanField(), ), ) - from hint.models import Hint - from hint.models import HintUse + from hint.models import Hint, HintUse x = challenges.prefetch_related( Prefetch( diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index b835d130..070d3969 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,8 +1,10 @@ import serpy from rest_framework import serializers -from challenge.models import Challenge, Category, File, Solve, Score, ChallengeFeedback, Tag -from challenge.sql import get_solve_counts, get_positive_votes, get_negative_votes +from challenge.models import (Category, Challenge, ChallengeFeedback, File, + Score, Solve, Tag) +from challenge.sql import (get_negative_votes, get_positive_votes, + get_solve_counts) from hint.serializers import FastHintSerializer diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index e989c61b..5a06fae6 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -1,21 +1,15 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.urls import reverse - -from rest_framework.status import ( - HTTP_200_OK, - HTTP_403_FORBIDDEN, - HTTP_401_UNAUTHORIZED, - HTTP_201_CREATED, - HTTP_400_BAD_REQUEST, -) +from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase -from config import config - from challenge.models import Solve -from hint.models import HintUse from challenge.tests.mixins import ChallengeSetupMixin +from config import config +from hint.models import HintUse class ChallengeTestCase(ChallengeSetupMixin, APITestCase): diff --git a/src/challenge/urls.py b/src/challenge/urls.py index 347aa251..eb4d0adf 100644 --- a/src/challenge/urls.py +++ b/src/challenge/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from challenge import views diff --git a/src/challenge/views.py b/src/challenge/views.py index 8fecafb7..80979f6b 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -5,8 +5,8 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.core.cache import caches -from django.db import transaction, models -from django.db.models import Prefetch, Case, When, Value, Sum +from django.db import models, transaction +from django.db.models import Case, Prefetch, Sum, Value, When from django.utils import timezone from rest_framework import permissions from rest_framework.generics import get_object_or_404 @@ -14,31 +14,27 @@ from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_400_BAD_REQUEST +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN from rest_framework.views import APIView from rest_framework.viewsets import ModelViewSet from backend.permissions import AdminOrReadOnly, IsBot, ReadOnlyBot from backend.response import FormattedResponse -from backend.signals import flag_submit, flag_reject, flag_score +from backend.signals import flag_reject, flag_score, flag_submit from backend.viewsets import AdminCreateModelViewSet -from challenge.models import Challenge, Category, Solve, File, ChallengeVote, ChallengeFeedback, Tag, Score +from challenge.models import (Category, Challenge, ChallengeFeedback, + ChallengeVote, File, Score, Solve, Tag) from challenge.permissions import CompetitionOpen -from challenge.serializers import ( - FileSerializer, - CreateCategorySerializer, - CreateChallengeSerializer, - ChallengeFeedbackSerializer, - TagSerializer, - AdminScoreSerializer, - FastCategorySerializer, - get_solve_counts, - get_positive_votes, - get_negative_votes, - FastChallengeSerializer, - FastAdminChallengeSerializer, - FastAdminCategorySerializer, -) +from challenge.serializers import (AdminScoreSerializer, + ChallengeFeedbackSerializer, + CreateCategorySerializer, + CreateChallengeSerializer, + FastAdminCategorySerializer, + FastAdminChallengeSerializer, + FastCategorySerializer, + FastChallengeSerializer, FileSerializer, + TagSerializer, get_negative_votes, + get_positive_votes, get_solve_counts) from config import config from hint.models import Hint, HintUse from team.models import Team diff --git a/src/config/models.py b/src/config/models.py index f7960a0b..45942cbf 100644 --- a/src/config/models.py +++ b/src/config/models.py @@ -1,5 +1,4 @@ from django.db import models - from django_prometheus.models import ExportModelOperationsMixin diff --git a/src/config/tests.py b/src/config/tests.py index 6e682ea9..ab76de8b 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -1,6 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT +from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, + HTTP_204_NO_CONTENT, HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase from config import config diff --git a/src/config/views.py b/src/config/views.py index 52c4e94f..e1ebfc42 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -1,9 +1,10 @@ -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_201_CREATED, HTTP_204_NO_CONTENT +from rest_framework.status import (HTTP_201_CREATED, HTTP_204_NO_CONTENT, + HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN) from rest_framework.views import APIView -from config import config -from backend.response import FormattedResponse from backend.permissions import AdminOrAnonymousReadOnly +from backend.response import FormattedResponse +from config import config class ConfigView(APIView): diff --git a/src/hint/migrations/0001_initial.py b/src/hint/migrations/0001_initial.py index ac09cd75..858cd025 100644 --- a/src/hint/migrations/0001_initial.py +++ b/src/hint/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/hint/migrations/0002_auto_20200808_1337.py b/src/hint/migrations/0002_auto_20200808_1337.py index 4c5c36ff..dd91eb50 100644 --- a/src/hint/migrations/0002_auto_20200808_1337.py +++ b/src/hint/migrations/0002_auto_20200808_1337.py @@ -1,8 +1,8 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/hint/models.py b/src/hint/models.py index 50007a62..a9ec6605 100644 --- a/src/hint/models.py +++ b/src/hint/models.py @@ -2,7 +2,6 @@ from django.db import models from django.db.models import CASCADE, SET_NULL from django.utils import timezone - from django_prometheus.models import ExportModelOperationsMixin from challenge.models import Challenge diff --git a/src/hint/tests.py b/src/hint/tests.py index c267d332..c532cdba 100644 --- a/src/hint/tests.py +++ b/src/hint/tests.py @@ -1,5 +1,6 @@ from rest_framework.reverse import reverse -from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_201_CREATED +from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, + HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase from challenge.tests.mixins import ChallengeSetupMixin diff --git a/src/hint/urls.py b/src/hint/urls.py index 1946fbe4..d7afa2de 100644 --- a/src/hint/urls.py +++ b/src/hint/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from hint import views diff --git a/src/hint/views.py b/src/hint/views.py index b704ffa3..992f201a 100644 --- a/src/hint/views.py +++ b/src/hint/views.py @@ -6,18 +6,14 @@ from backend.permissions import IsBot from backend.response import FormattedResponse +from backend.signals import use_hint from backend.viewsets import AdminCreateModelViewSet from challenge.permissions import CompetitionOpen from challenge.views import get_cache_key from hint.models import Hint, HintUse from hint.permissions import HasUsedHint -from hint.serializers import ( - FullHintSerializer, - HintSerializer, - UseHintSerializer, - CreateHintSerializer, -) -from backend.signals import use_hint +from hint.serializers import (CreateHintSerializer, FullHintSerializer, + HintSerializer, UseHintSerializer) from team.permissions import HasTeam diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index ec2e9265..9fedde0b 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -3,9 +3,10 @@ from rest_framework.status import HTTP_200_OK from rest_framework.test import APITestCase -from challenge.models import Score, Solve, Category, Challenge +from challenge.models import Category, Challenge, Score, Solve from config import config -from leaderboard.views import UserListView, TeamListView, GraphView, CTFTimeListView +from leaderboard.views import (CTFTimeListView, GraphView, TeamListView, + UserListView) from team.models import Team diff --git a/src/leaderboard/urls.py b/src/leaderboard/urls.py index 475bb570..c0a985b4 100644 --- a/src/leaderboard/urls.py +++ b/src/leaderboard/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from leaderboard import views diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index e02f6302..0a11e743 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.core.cache import caches from rest_framework.generics import ListAPIView -from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer +from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import ReadOnlyModelViewSet @@ -11,7 +11,11 @@ from backend.response import FormattedResponse from challenge.models import Score from config import config -from leaderboard.serializers import LeaderboardUserScoreSerializer, LeaderboardTeamScoreSerializer, UserPointsSerializer, TeamPointsSerializer, CTFTimeSerializer, MatrixSerializer +from leaderboard.serializers import (CTFTimeSerializer, + LeaderboardTeamScoreSerializer, + LeaderboardUserScoreSerializer, + MatrixSerializer, TeamPointsSerializer, + UserPointsSerializer) from team.models import Team diff --git a/src/member/migrations/0001_initial.py b/src/member/migrations/0001_initial.py index 2e17c710..8a970926 100644 --- a/src/member/migrations/0001_initial.py +++ b/src/member/migrations/0001_initial.py @@ -1,14 +1,16 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 -import backend.validators -from django.conf import settings +import secrets + import django.contrib.auth.models import django.contrib.postgres.fields.citext -from django.contrib.postgres.operations import CITextExtension -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone -import secrets +from django.conf import settings +from django.contrib.postgres.operations import CITextExtension +from django.db import migrations, models + +import backend.validators class Migration(migrations.Migration): diff --git a/src/member/migrations/0002_auto_20200808_1337.py b/src/member/migrations/0002_auto_20200808_1337.py index 427a3dfa..f2011df4 100644 --- a/src/member/migrations/0002_auto_20200808_1337.py +++ b/src/member/migrations/0002_auto_20200808_1337.py @@ -1,7 +1,7 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/member/migrations/0003_auto_20200808_1649.py b/src/member/migrations/0003_auto_20200808_1649.py index f4c43420..531ec11c 100644 --- a/src/member/migrations/0003_auto_20200808_1649.py +++ b/src/member/migrations/0003_auto_20200808_1649.py @@ -1,9 +1,10 @@ # Generated by Django 3.0.5 on 2020-08-08 15:49 -import backend.validators import django.contrib.postgres.fields.citext from django.db import migrations +import backend.validators + class Migration(migrations.Migration): diff --git a/src/member/models.py b/src/member/models.py index c7d19e5f..bb1c7061 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -9,11 +9,10 @@ from django.db.models import SET_NULL from django.utils import timezone from django.utils.translation import gettext_lazy as _ - from django_prometheus.models import ExportModelOperationsMixin -from config import config from backend.validators import printable_name +from config import config class TOTPStatus(IntEnum): diff --git a/src/member/serializers.py b/src/member/serializers.py index 4c1bd43c..7092ea6c 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -5,8 +5,8 @@ from backend.mixins import IncorrectSolvesMixin from challenge.serializers import SolveSerializer -from member.models import UserIP from config import config +from member.models import UserIP class MemberSerializer(IncorrectSolvesMixin, serializers.ModelSerializer): diff --git a/src/member/tests.py b/src/member/tests.py index ee69594a..da213ee3 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -1,11 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import ( - HTTP_200_OK, - HTTP_403_FORBIDDEN, - HTTP_401_UNAUTHORIZED, - HTTP_400_BAD_REQUEST, -) +from rest_framework.status import (HTTP_200_OK, HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase diff --git a/src/member/urls.py b/src/member/urls.py index 7c55c287..8e114c0a 100644 --- a/src/member/urls.py +++ b/src/member/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from member import views diff --git a/src/member/views.py b/src/member/views.py index ad5a59a1..3bb9ce56 100644 --- a/src/member/views.py +++ b/src/member/views.py @@ -1,13 +1,15 @@ from django.contrib.auth import get_user_model from rest_framework import filters from rest_framework.generics import RetrieveUpdateAPIView -from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.viewsets import ModelViewSet from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.viewsets import AdminListModelViewSet from member.models import UserIP -from member.serializers import SelfSerializer, MemberSerializer, AdminMemberSerializer, ListMemberSerializer, UserIPSerializer +from member.serializers import (AdminMemberSerializer, ListMemberSerializer, + MemberSerializer, SelfSerializer, + UserIPSerializer) class SelfView(RetrieveUpdateAPIView): diff --git a/src/pages/models.py b/src/pages/models.py index 55531b48..bde2752b 100644 --- a/src/pages/models.py +++ b/src/pages/models.py @@ -1,5 +1,4 @@ from django.db import models - from django_prometheus.models import ExportModelOperationsMixin diff --git a/src/pages/serializers.py b/src/pages/serializers.py index 8aae7fc6..143973ee 100644 --- a/src/pages/serializers.py +++ b/src/pages/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers + from pages.models import Page diff --git a/src/pages/urls.py b/src/pages/urls.py index b8303e59..93400892 100644 --- a/src/pages/urls.py +++ b/src/pages/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from pages import views diff --git a/src/pages/views.py b/src/pages/views.py index a553467b..70832fbc 100644 --- a/src/pages/views.py +++ b/src/pages/views.py @@ -1,8 +1,8 @@ from rest_framework.viewsets import ModelViewSet +from backend.permissions import AdminOrAnonymousReadOnly from pages.models import Page from pages.serializers import PageSerializer -from backend.permissions import AdminOrAnonymousReadOnly class TagViewSet(ModelViewSet): diff --git a/src/plugins/flag/lenient.py b/src/plugins/flag/lenient.py index 930e5174..fdee42ad 100644 --- a/src/plugins/flag/lenient.py +++ b/src/plugins/flag/lenient.py @@ -1,6 +1,7 @@ +import unicodedata + from config import config from plugins.flag.base import FlagPlugin -import unicodedata def strip_accents(s): diff --git a/src/plugins/flag/map.py b/src/plugins/flag/map.py index d48c46f2..9bb5e6f1 100644 --- a/src/plugins/flag/map.py +++ b/src/plugins/flag/map.py @@ -1,7 +1,7 @@ -from plugins.flag.base import FlagPlugin - import math +from plugins.flag.base import FlagPlugin + class MapFlagPlugin(FlagPlugin): name = "map" diff --git a/src/plugins/models.py b/src/plugins/models.py index 97e03b3e..6a40c35b 100644 --- a/src/plugins/models.py +++ b/src/plugins/models.py @@ -1,4 +1,5 @@ from django.conf import settings + from plugins import plugins plugins.load_plugins(settings.INSTALLED_PLUGINS) diff --git a/src/plugins/plugins.py b/src/plugins/plugins.py index 37db1a0e..3ab6569f 100644 --- a/src/plugins/plugins.py +++ b/src/plugins/plugins.py @@ -1,5 +1,5 @@ -import sys import inspect +import sys from collections import defaultdict from pydoc import locate diff --git a/src/plugins/points/base.py b/src/plugins/points/base.py index 9072b7e1..1af7ec07 100644 --- a/src/plugins/points/base.py +++ b/src/plugins/points/base.py @@ -1,11 +1,11 @@ import abc import time -from django.db.models import Sum, F +from django.db.models import F, Sum from django.utils import timezone -from config import config from challenge.models import Score, Solve +from config import config from hint.models import HintUse from plugins.base import Plugin diff --git a/src/plugins/tests.py b/src/plugins/tests.py index 671075ef..84d054fa 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework.test import APITestCase -from challenge.models import Category, Challenge, Solve, Score +from challenge.models import Category, Challenge, Score, Solve from challenge.tests.mixins import ChallengeSetupMixin from config import config from plugins import plugins diff --git a/src/polaris/urls.py b/src/polaris/urls.py index e23b1593..eafea02d 100644 --- a/src/polaris/urls.py +++ b/src/polaris/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from polaris import views diff --git a/src/polaris/views.py b/src/polaris/views.py index 228c53cd..81cb8dc4 100644 --- a/src/polaris/views.py +++ b/src/polaris/views.py @@ -1,6 +1,6 @@ from rest_framework import viewsets from rest_framework.generics import get_object_or_404 -from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.views import APIView from backend.response import FormattedResponse diff --git a/src/ractf/apps.py b/src/ractf/apps.py index 9f39bdd3..031f03bf 100644 --- a/src/ractf/apps.py +++ b/src/ractf/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -from django.core.checks import CheckMessage, register, WARNING, ERROR from django.conf import settings +from django.core.checks import ERROR, WARNING, CheckMessage, register class RactfConfig(AppConfig): diff --git a/src/ractf/management/commands/flush_db.py b/src/ractf/management/commands/flush_db.py index 19b2528d..29c1354f 100644 --- a/src/ractf/management/commands/flush_db.py +++ b/src/ractf/management/commands/flush_db.py @@ -1,11 +1,10 @@ import sys import psycopg2 +from django.conf import settings from django.core.management import BaseCommand from django.core.management.commands import migrate -from django.conf import settings - class Command(BaseCommand): help = "Resets the database to the default configuration" diff --git a/src/ractf/management/commands/getschema.py b/src/ractf/management/commands/getschema.py index 6d9602dd..dd070660 100644 --- a/src/ractf/management/commands/getschema.py +++ b/src/ractf/management/commands/getschema.py @@ -1,10 +1,8 @@ import os from io import StringIO -from django.core.management import BaseCommand -from django.core.management import call_command - import yaml +from django.core.management import BaseCommand, call_command class Command(BaseCommand): diff --git a/src/ractf/management/commands/insert_dummy_data.py b/src/ractf/management/commands/insert_dummy_data.py index 53d778fe..29c1db20 100644 --- a/src/ractf/management/commands/insert_dummy_data.py +++ b/src/ractf/management/commands/insert_dummy_data.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from django.core.management import BaseCommand -from challenge.models import Challenge, Category +from challenge.models import Category, Challenge from hint.models import Hint from team.models import Team diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index dc8c477c..803508ed 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model from django.core.management import BaseCommand -from challenge.models import Challenge, Category, Score, Solve +from challenge.models import Category, Challenge, Score, Solve from team.models import Team diff --git a/src/ractf/management/commands/reset_scores.py b/src/ractf/management/commands/reset_scores.py index 1ab138eb..634ef8d9 100644 --- a/src/ractf/management/commands/reset_scores.py +++ b/src/ractf/management/commands/reset_scores.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from django.core.management import BaseCommand -from challenge.models import Score, Solve, Challenge +from challenge.models import Challenge, Score, Solve from team.models import Team diff --git a/src/scorerecalculator/tests.py b/src/scorerecalculator/tests.py index 326405ca..4fb8fc45 100644 --- a/src/scorerecalculator/tests.py +++ b/src/scorerecalculator/tests.py @@ -2,7 +2,8 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, HTTP_401_UNAUTHORIZED +from rest_framework.status import (HTTP_200_OK, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase from challenge.models import Score diff --git a/src/sockets/consumers.py b/src/sockets/consumers.py index 61123cf7..7abeaf63 100644 --- a/src/sockets/consumers.py +++ b/src/sockets/consumers.py @@ -2,8 +2,8 @@ from asgiref.sync import sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer -from authentication.models import Token +from authentication.models import Token from backend.signals import websocket_connect, websocket_disconnect diff --git a/src/sockets/signals.py b/src/sockets/signals.py index af16a1a3..4787b265 100644 --- a/src/sockets/signals.py +++ b/src/sockets/signals.py @@ -3,11 +3,11 @@ from django.db.models.signals import post_save from django.dispatch import receiver -from config import config from announcements.models import Announcement from announcements.serializers import AnnouncementSerializer -from backend.signals import flag_score, flag_reject, use_hint, team_join +from backend.signals import flag_reject, flag_score, team_join, use_hint from challenge.models import Challenge +from config import config def get_team_channel(user): diff --git a/src/stats/apps.py b/src/stats/apps.py index 2ad9584e..ec955afc 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -3,7 +3,9 @@ from django.apps import AppConfig -import team, member, challenge +import challenge +import member +import team class StatsConfig(AppConfig): diff --git a/src/stats/signals.py b/src/stats/signals.py index bf70edf0..8721dd66 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -1,14 +1,14 @@ from django.core.cache import cache -from django.dispatch import receiver from django.db.models.signals import post_delete +from django.dispatch import receiver from prometheus_client import Gauge -from backend.signals import websocket_disconnect, websocket_connect, team_create, flag_score, register +from backend.signals import (flag_score, register, team_create, + websocket_connect, websocket_disconnect) from challenge.models import Solve from member.models import Member from team.models import Team - member_count = Gauge("member_count", "The number of members currently registered") team_count = Gauge("team_count", "The number of teams currently registered") solve_count = Gauge("solve_count", "The count of both correct and incorrect solves") diff --git a/src/stats/tests.py b/src/stats/tests.py index 30226c16..a92138b0 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -1,10 +1,11 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED +from rest_framework.status import (HTTP_200_OK, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN) from rest_framework.test import APITestCase +from challenge.models import Category, Challenge, Solve from config import config -from challenge.models import Challenge, Category, Solve from team.models import Team diff --git a/src/stats/views.py b/src/stats/views.py index e97fed4d..f976c58d 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -1,5 +1,5 @@ import os -from datetime import timezone, datetime +from datetime import datetime, timezone from django.contrib.auth import get_user_model from django.core.cache import cache @@ -9,12 +9,13 @@ from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView -from config import config from backend.response import FormattedResponse from challenge.models import Score from challenge.sql import get_incorrect_solve_counts, get_solve_counts +from config import config from member.models import UserIP -from stats.signals import member_count, team_count, solve_count, correct_solve_count +from stats.signals import (correct_solve_count, member_count, solve_count, + team_count) from team.models import Team diff --git a/src/team/migrations/0001_initial.py b/src/team/migrations/0001_initial.py index 42c86d26..a79b9286 100644 --- a/src/team/migrations/0001_initial.py +++ b/src/team/migrations/0001_initial.py @@ -1,11 +1,12 @@ # Generated by Django 3.0.5 on 2020-08-08 13:37 -import backend.validators -from django.conf import settings import django.contrib.postgres.fields.citext -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + +import backend.validators class Migration(migrations.Migration): diff --git a/src/team/migrations/0002_auto_20200808_1649.py b/src/team/migrations/0002_auto_20200808_1649.py index 2ced8bd2..8f7519e0 100644 --- a/src/team/migrations/0002_auto_20200808_1649.py +++ b/src/team/migrations/0002_auto_20200808_1649.py @@ -1,9 +1,10 @@ # Generated by Django 3.0.5 on 2020-08-08 15:49 -import backend.validators import django.contrib.postgres.fields.citext from django.db import migrations +import backend.validators + class Migration(migrations.Migration): diff --git a/src/team/models.py b/src/team/models.py index 8babd5a4..f35796de 100644 --- a/src/team/models.py +++ b/src/team/models.py @@ -2,7 +2,6 @@ from django.db import models from django.db.models import CASCADE, Prefetch from django.utils import timezone - from django_prometheus.models import ExportModelOperationsMixin from backend.validators import printable_name diff --git a/src/team/serializers.py b/src/team/serializers.py index 4c6ad73f..5bb2a98a 100644 --- a/src/team/serializers.py +++ b/src/team/serializers.py @@ -1,9 +1,9 @@ from rest_framework import serializers from backend.mixins import IncorrectSolvesMixin +from backend.signals import team_create from challenge.serializers import SolveSerializer from member.serializers import MinimalMemberSerializer -from backend.signals import team_create from team.models import Team diff --git a/src/team/tests.py b/src/team/tests.py index 2e9daeba..014554f0 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -1,17 +1,12 @@ from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import ( - HTTP_200_OK, - HTTP_404_NOT_FOUND, - HTTP_201_CREATED, - HTTP_403_FORBIDDEN, - HTTP_401_UNAUTHORIZED, - HTTP_400_BAD_REQUEST, -) +from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND) from rest_framework.test import APITestCase +from challenge.models import Category, Challenge, Solve from config import config -from challenge.models import Solve, Category, Challenge from team.models import Team diff --git a/src/team/urls.py b/src/team/urls.py index a40d88ab..d27e23b9 100644 --- a/src/team/urls.py +++ b/src/team/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter from team import views diff --git a/src/team/views.py b/src/team/views.py index 8c1adf57..e62e6660 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -1,31 +1,25 @@ from django.http import Http404 from rest_framework import filters -from rest_framework.generics import ( - RetrieveUpdateAPIView, - CreateAPIView, - get_object_or_404, -) +from rest_framework.generics import (CreateAPIView, RetrieveUpdateAPIView, + get_object_or_404) from rest_framework.permissions import IsAuthenticated -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND +from rest_framework.status import (HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, + HTTP_404_NOT_FOUND) from rest_framework.views import APIView -from config import config from backend.exceptions import FormattedException from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.response import FormattedResponse -from backend.signals import team_join_attempt, team_join_reject, team_join +from backend.signals import team_join, team_join_attempt, team_join_reject from backend.viewsets import AdminListModelViewSet -from team.models import Team from challenge.models import Solve +from config import config from member.models import Member -from team.permissions import IsTeamOwnerOrReadOnly, HasTeam, TeamsEnabled -from team.serializers import ( - SelfTeamSerializer, - TeamSerializer, - CreateTeamSerializer, - AdminTeamSerializer, - ListTeamSerializer, -) +from team.models import Team +from team.permissions import HasTeam, IsTeamOwnerOrReadOnly, TeamsEnabled +from team.serializers import (AdminTeamSerializer, CreateTeamSerializer, + ListTeamSerializer, SelfTeamSerializer, + TeamSerializer) class SelfView(RetrieveUpdateAPIView): From 9e6b00e84e6a34651bbe424fa885b7f985d24b4b Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:28:26 +0100 Subject: [PATCH 066/185] Add flake8 to dev dependencies --- poetry.lock | 31 ++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 82f035bf..5f1db93a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -539,6 +539,19 @@ mypy = ">=0.790" requests = ">=2.0.0" typing-extensions = ">=3.7.2" +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + [[package]] name = "gprof2dot" version = "2021.2.21" @@ -743,6 +756,14 @@ python-versions = ">=3.5" [package.dependencies] traitlets = "*" +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "msgpack" version = "0.6.2" @@ -1416,7 +1437,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "867fb1b06fa9888e6ac9c929a18da80e5b600c88a7b01af79be2f74c9a9b0d45" +content-hash = "5876b7e63ef1490e214522c8a7821bff8afd7657ba4554ea5fa52ceb29d43051" [metadata.files] aioredis = [ @@ -1683,6 +1704,10 @@ djangorestframework-stubs = [ {file = "djangorestframework-stubs-1.4.0.tar.gz", hash = "sha256:037f0582b1e6c79366b6a839da861474d59210c4bfa1d36291545cb6ede6a0da"}, {file = "djangorestframework_stubs-1.4.0-py3-none-any.whl", hash = "sha256:f6ed5fb19c12aa752288ddc6ad28d4ca7c81681ca7f28a19aba9064b2a69489c"}, ] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] gprof2dot = [ {file = "gprof2dot-2021.2.21.tar.gz", hash = "sha256:1223189383b53dcc8ecfd45787ac48c0ed7b4dbc16ee8b88695d053eea1acabf"}, ] @@ -1838,6 +1863,10 @@ matplotlib-inline = [ {file = "matplotlib-inline-0.1.2.tar.gz", hash = "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"}, {file = "matplotlib_inline-0.1.2-py3-none-any.whl", hash = "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811"}, ] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] msgpack = [ {file = "msgpack-0.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170"}, {file = "msgpack-0.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a06efd0482a1942aad209a6c18321b5e22d64eb531ea20af138b28172d8f35ba"}, diff --git a/pyproject.toml b/pyproject.toml index 22f74cd8..b0a92624 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ pytest = "^6.2.4" pytest-cov = "^2.12.0" pytest-django = "^4.3.0" isort = "^5.8.0" +flake8 = "^3.9.2" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "backend.settings.local" From b3a6d2c50de9f1f5f7218ab0367e01150a83e552 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:35:23 +0100 Subject: [PATCH 067/185] Add pre-commit to dev dependencies --- poetry.lock | 107 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 5f1db93a..2e235f06 100644 --- a/poetry.lock +++ b/poetry.lock @@ -208,6 +208,14 @@ python-versions = "*" [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.3.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + [[package]] name = "channels" version = "2.4.0" @@ -353,6 +361,14 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "distlib" +version = "0.3.2" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "django" version = "3.2.4" @@ -539,6 +555,14 @@ mypy = ">=0.790" requests = ">=2.0.0" typing-extensions = ">=3.7.2" +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "flake8" version = "3.9.2" @@ -612,6 +636,17 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] idna = ">=2.5" +[[package]] +name = "identify" +version = "2.2.10" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["editdistance-s"] + [[package]] name = "idna" version = "2.10" @@ -807,6 +842,14 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.extras] infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "20.9" @@ -868,6 +911,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] dev = ["pre-commit", "tox"] +[[package]] +name = "pre-commit" +version = "2.13.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + [[package]] name = "prometheus-client" version = "0.11.0" @@ -1389,6 +1448,24 @@ dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0 docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +[[package]] +name = "virtualenv" +version = "20.4.7" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + [[package]] name = "watchgod" version = "0.7" @@ -1437,7 +1514,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "5876b7e63ef1490e214522c8a7821bff8afd7657ba4554ea5fa52ceb29d43051" +content-hash = "1d60e5297a6b6ca27656c1e84c19d89aece02e8d11fde97071c6a6e49c7c9cdc" [metadata.files] aioredis = [ @@ -1541,6 +1618,10 @@ cffi = [ {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ] +cfgv = [ + {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, + {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, +] channels = [ {file = "channels-2.4.0-py2.py3-none-any.whl", hash = "sha256:80a5ad1962ae039a3dcc0a5cb5212413e66e2f11ad9e9db8004834436daf3400"}, {file = "channels-2.4.0.tar.gz", hash = "sha256:08e756406d7165cb32f6fc3090c0643f41ca9f7e0f7fada0b31194662f20f414"}, @@ -1649,6 +1730,10 @@ decorator = [ {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, ] +distlib = [ + {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, + {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, +] django = [ {file = "Django-3.2.4-py3-none-any.whl", hash = "sha256:ea735cbbbb3b2fba6d4da4784a0043d84c67c92f1fdf15ad6db69900e792c10f"}, {file = "Django-3.2.4.tar.gz", hash = "sha256:66c9d8db8cc6fe938a28b7887c1596e42d522e27618562517cc8929eb7e7f296"}, @@ -1704,6 +1789,10 @@ djangorestframework-stubs = [ {file = "djangorestframework-stubs-1.4.0.tar.gz", hash = "sha256:037f0582b1e6c79366b6a839da861474d59210c4bfa1d36291545cb6ede6a0da"}, {file = "djangorestframework_stubs-1.4.0-py3-none-any.whl", hash = "sha256:f6ed5fb19c12aa752288ddc6ad28d4ca7c81681ca7f28a19aba9064b2a69489c"}, ] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -1783,6 +1872,10 @@ hyperlink = [ {file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"}, {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"}, ] +identify = [ + {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"}, + {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"}, +] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, @@ -1933,6 +2026,10 @@ newrelic = [ {file = "newrelic-5.24.0.153-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:76ca893803d7a738844e5a49f51bde36fa98c8f55c203d2bf9a7f246770689c6"}, {file = "newrelic-5.24.0.153.tar.gz", hash = "sha256:a8ef9509258a8f0c952fe36c7d3c774552b65a5ca5e0348694b651170b8472dc"}, ] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, @@ -1957,6 +2054,10 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] +pre-commit = [ + {file = "pre_commit-2.13.0-py2.py3-none-any.whl", hash = "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"}, + {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"}, +] prometheus-client = [ {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, @@ -2310,6 +2411,10 @@ uvloop = [ {file = "uvloop-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c"}, {file = "uvloop-0.15.2.tar.gz", hash = "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01"}, ] +virtualenv = [ + {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, + {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, +] watchgod = [ {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, diff --git a/pyproject.toml b/pyproject.toml index b0a92624..3f1d8119 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ pytest-cov = "^2.12.0" pytest-django = "^4.3.0" isort = "^5.8.0" flake8 = "^3.9.2" +pre-commit = "^2.13.0" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "backend.settings.local" From 66861b636e73623f2b14954becad4efc4c72f0f1 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:36:59 +0100 Subject: [PATCH 068/185] Remove explicit 'Twisted' pin --- poetry.lock | 95 ++++++++++++++++++++++++-------------------------- pyproject.toml | 1 - 2 files changed, 45 insertions(+), 51 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e235f06..a4ff10a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1024,14 +1024,6 @@ category = "main" optional = false python-versions = ">=3.5" -[[package]] -name = "pyhamcrest" -version = "2.0.2" -description = "Hamcrest framework for matcher objects" -category = "main" -optional = false -python-versions = ">=3.5" - [[package]] name = "pyopenssl" version = "20.0.1" @@ -1333,35 +1325,45 @@ test = ["pytest"] [[package]] name = "twisted" -version = "20.3.0" +version = "21.2.0" description = "An asynchronous networking framework written in Python" category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.5.4" [package.dependencies] attrs = ">=19.2.0" -Automat = ">=0.3.0" +Automat = ">=0.8.0" constantly = ">=15.1" hyperlink = ">=17.1.1" -idna = {version = ">=0.6,<2.3 || >2.3", optional = true, markers = "extra == \"tls\""} +idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" -PyHamcrest = ">=1.9.0,<1.10.0 || >1.10.0" pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} -service_identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} +service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} +twisted-iocpsupport = {version = ">=1.0.0,<1.1.0", markers = "platform_system == \"Windows\""} "zope.interface" = ">=4.4.2" [package.extras] -all_non_platform = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] -conch = ["pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] -dev = ["pyflakes (>=1.0.0)", "twisted-dev-tools (>=0.0.2)", "python-subunit", "sphinx (>=1.3.1)", "towncrier (>=17.4.0)"] +all_non_platform = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] +conch = ["pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] +contextvars = ["contextvars (>=2.4,<3)"] +dev = ["towncrier (>=17.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=3.3,<4.0)", "pyflakes (>=1.0.0)", "python-subunit", "twistedchecker (>=0.7.2)", "pydoctor (>=20.12.1)"] +dev_release = ["towncrier (>=17.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=3.3,<4.0)", "pydoctor (>=20.12.1)"] http2 = ["h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)"] -macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] -osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] +osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] -soap = ["soappy"] -tls = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)"] -windows_platform = ["pywin32 (!=226)", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +test = ["cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)"] +tls = ["pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)"] +windows_platform = ["pywin32 (!=226)", "cython-test-exception-raiser (>=1.0,<2.0)", "PyHamcrest (>=1.9.0)", "pyopenssl (>=16.0.0)", "service-identity (>=18.1.0)", "idna (>=2.4)", "pyasn1", "cryptography (>=2.6)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)", "contextvars (>=2.4,<3)"] + +[[package]] +name = "twisted-iocpsupport" +version = "1.0.1" +description = "An extension for use in the twisted I/O Completion Ports reactor." +category = "main" +optional = false +python-versions = "*" [[package]] name = "txaio" @@ -1514,7 +1516,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "1d60e5297a6b6ca27656c1e84c19d89aece02e8d11fde97071c6a6e49c7c9cdc" +content-hash = "e5bc834af15dccf632582ad50d6ee8ec15df2264274d146446c880acd26ac57b" [metadata.files] aioredis = [ @@ -2157,10 +2159,6 @@ pygments = [ {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, ] -pyhamcrest = [ - {file = "PyHamcrest-2.0.2-py3-none-any.whl", hash = "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"}, - {file = "PyHamcrest-2.0.2.tar.gz", hash = "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316"}, -] pyopenssl = [ {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, {file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"}, @@ -2322,29 +2320,26 @@ traitlets = [ {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, ] twisted = [ - {file = "Twisted-20.3.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:cdbc4c7f0cd7a2218b575844e970f05a1be1861c607b0e048c9bceca0c4d42f7"}, - {file = "Twisted-20.3.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d267125cc0f1e8a0eed6319ba4ac7477da9b78a535601c49ecd20c875576433a"}, - {file = "Twisted-20.3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:356e8d8dd3590e790e3dba4db139eb8a17aca64b46629c622e1b1597a4a92478"}, - {file = "Twisted-20.3.0-cp27-cp27m-win32.whl", hash = "sha256:ca3a0b8c9110800e576d89b5337373e52018b41069bc879f12fa42b7eb2d0274"}, - {file = "Twisted-20.3.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cd1dc5c85b58494138a3917752b54bb1daa0045d234b7c132c37a61d5483ebad"}, - {file = "Twisted-20.3.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:94ac3d55a58c90e2075c5fe1853f2aa3892b73e3bf56395f743aefde8605eeaa"}, - {file = "Twisted-20.3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7408c6635ee1b96587289283ebe90ee15dbf9614b05857b446055116bc822d29"}, - {file = "Twisted-20.3.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c09c47ff9750a8e3aa60ad169c4b95006d455a29b80ad0901f031a103b2991cd"}, - {file = "Twisted-20.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:158ddb80719a4813d292293ac44ba41d8b56555ed009d90994a278237ee63d2c"}, - {file = "Twisted-20.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:040eb6641125d2a9a09cf198ec7b83dd8858c6f51f6770325ed9959c00f5098f"}, - {file = "Twisted-20.3.0-cp35-cp35m-win32.whl", hash = "sha256:147780b8caf21ba2aef3688628eaf13d7e7fe02a86747cd54bfaf2140538f042"}, - {file = "Twisted-20.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:25ffcf37944bdad4a99981bc74006d735a678d2b5c193781254fbbb6d69e3b22"}, - {file = "Twisted-20.3.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:a58e61a2a01e5bcbe3b575c0099a2bcb8d70a75b1a087338e0c48dd6e01a5f15"}, - {file = "Twisted-20.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7c547fd0215db9da8a1bc23182b309e84a232364cc26d829e9ee196ce840b114"}, - {file = "Twisted-20.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2182000d6ffc05d269e6c03bfcec8b57e20259ca1086180edaedec3f1e689292"}, - {file = "Twisted-20.3.0-cp36-cp36m-win32.whl", hash = "sha256:70952c56e4965b9f53b180daecf20a9595cf22b8d0935cd3bd664c90273c3ab2"}, - {file = "Twisted-20.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3281d9ce889f7b21bdb73658e887141aa45a102baf3b2320eafcfba954fcefec"}, - {file = "Twisted-20.3.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:e92703bed0cc21d6cb5c61d66922b3b1564015ca8a51325bd164a5e33798d504"}, - {file = "Twisted-20.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467"}, - {file = "Twisted-20.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:894f6f3cfa57a15ea0d0714e4283913a5f2511dbd18653dd148eba53b3919797"}, - {file = "Twisted-20.3.0-cp37-cp37m-win32.whl", hash = "sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4"}, - {file = "Twisted-20.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d95803193561a243cb0401b0567c6b7987d3f2a67046770e1dccd1c9e49a9780"}, - {file = "Twisted-20.3.0.tar.bz2", hash = "sha256:d72c55b5d56e176563b91d11952d13b01af8725c623e498db5507b6614fc1e10"}, + {file = "Twisted-21.2.0-py3-none-any.whl", hash = "sha256:aab38085ea6cda5b378b519a0ec99986874921ee8881318626b0a3414bb2631e"}, + {file = "Twisted-21.2.0.tar.gz", hash = "sha256:77544a8945cf69b98d2946689bbe0c75de7d145cdf11f391dd487eae8fc95a12"}, +] +twisted-iocpsupport = [ + {file = "twisted-iocpsupport-1.0.1.tar.gz", hash = "sha256:bdda01692988f29d43d832a4b1f402199d0c7d80083ad65059a0892a3670fc9a"}, + {file = "twisted_iocpsupport-1.0.1-cp27-cp27m-win32.whl", hash = "sha256:34a3dfd743f9b7a464ba72c9fa9fc21e2a5e5d2f5a8c805f3e3057a2b3c1f7de"}, + {file = "twisted_iocpsupport-1.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e9cfab3e9dbc3a541722b7e34656e3694a7a09d113c40b51213e8d8a5b6a2314"}, + {file = "twisted_iocpsupport-1.0.1-cp35-cp35m-win32.whl", hash = "sha256:28770b4d0da9364df81371e962323a8390b181eeeccec91eb21316489a4ee337"}, + {file = "twisted_iocpsupport-1.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:95595fb8d242b802b21f2c0652af0997287dcba67f4f85285be9adc15572e462"}, + {file = "twisted_iocpsupport-1.0.1-cp36-cp36m-win32.whl", hash = "sha256:de5a2273df46fafab430fadfe0213943ffbc0cdb8ab5d49cb41e0610043ee70e"}, + {file = "twisted_iocpsupport-1.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f3c505de2237ed47c20e425dfc903a2ee1ee2cf08654f2c88898e50a60a5f210"}, + {file = "twisted_iocpsupport-1.0.1-cp37-cp37m-win32.whl", hash = "sha256:90e6f15acf86de2b66e1441fb1c4bcbba4e1361b799fa5c20899e453eb66c5c9"}, + {file = "twisted_iocpsupport-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9fd9b059545536d5f7ac10f642ebba0437203e884479eb9d01d636dd287414"}, + {file = "twisted_iocpsupport-1.0.1-cp38-cp38-win32.whl", hash = "sha256:e1dae2dd44a11153b169dda012b222bce524792dc64859ab2b86deb7a0915863"}, + {file = "twisted_iocpsupport-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f5c45094f16c1c0749453a9341bfe818cb5f4543e76ae744bc983c992a0abdd"}, + {file = "twisted_iocpsupport-1.0.1-cp39-cp39-win32.whl", hash = "sha256:5b9ce8803023d0818ab2507fe222c16c193bae091abb59a45841a757ffe198fe"}, + {file = "twisted_iocpsupport-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8dab94de3a9bafae1e3ec92aec01f1239b2b4b4f3866162f2c5f2e649e2661dc"}, + {file = "twisted_iocpsupport-1.0.1-pp27-pypy_73-win32.whl", hash = "sha256:eb033bed4f20d2053767054c33be4d8ae09faf23d547681ecd5ff463d97bc099"}, + {file = "twisted_iocpsupport-1.0.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:ed8cb959d7dce287419e766267a280139fedf14217d85695d7b6384d8ccd3353"}, + {file = "twisted_iocpsupport-1.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:1535e537b6d60d0cfd2b4785f1c7a021beb62d8df6bac3dc6c45bb866ce648e4"}, ] txaio = [ {file = "txaio-21.2.1-py2.py3-none-any.whl", hash = "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"}, diff --git a/pyproject.toml b/pyproject.toml index 3f1d8119..07ee6fa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ django-cachalot = "^2.3.5" django-silk = "^4.1.0" serpy = "^0.3.1" django-zxcvbn-password-validator = "^1.3.2" -Twisted = {version = "20.3.0", platform = "linux"} uvicorn = {extras = ["standard"], version = "^0.13.4"} sentry-sdk = "^1.0.0" From edb8cb5d6e1f6b1b8cd9c21aded3cd226a47aff9 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:38:29 +0100 Subject: [PATCH 069/185] Add .pre-commit-config.yaml --- .pre-commit-config.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..82aebde7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: local + hooks: + - id: poetry-lint + name: Poetry Lint + description: This hook runs `make lint` inside a relevant poetry environment. + entry: poetry run make lint + language: system + types: [python] + + - id: poetry-test + name: Poetry Test + description: This hook runs `make test` inside a relevant poetry environment. + entry: poetry run make test + language: system + types: [python] From 8a0165bfb80fa9b0ee820c2a2553b1c86e8da7b8 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:43:29 +0100 Subject: [PATCH 070/185] Add backend/settings/lint.py --- src/backend/settings/lint.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/backend/settings/lint.py diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py new file mode 100644 index 00000000..e0143b98 --- /dev/null +++ b/src/backend/settings/lint.py @@ -0,0 +1,33 @@ +from . import * + +SECRET_KEY = "CorrectHorseBatteryStaple" + +MAIL["SEND"] = False + +FRONTEND_URL = "http://example.com/" +DOMAIN = "example.com" + +for scope in REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]: + REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"][scope] = "9999999/minute" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "/tmp/db.sqlite3", + "USER": "", + "PASSWORD": "", + "HOST": "", + "PORT": "", + }, +} + +CONFIG = { + "BACKEND": "config.backends.DatabaseBackend", +} + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "dead-beef", + } +} From df6969496a5df48c6761605bb1c1ca001b8fda13 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:52:50 +0100 Subject: [PATCH 071/185] Run black --- src/admin/views.py | 1 + src/authentication/basic_auth.py | 6 ++---- src/authentication/serializers.py | 3 +-- src/authentication/tests.py | 26 +++++++++++++++----------- src/authentication/views.py | 29 +++++++++++++---------------- src/challenge/models.py | 3 +-- src/challenge/serializers.py | 6 ++---- src/challenge/tests/test_views.py | 4 +--- src/challenge/views.py | 28 ++++++++++++++++------------ src/config/tests.py | 3 +-- src/config/views.py | 3 +-- src/hint/tests.py | 3 +-- src/hint/views.py | 3 +-- src/leaderboard/tests.py | 3 +-- src/leaderboard/views.py | 10 ++-------- src/member/tests.py | 3 +-- src/member/views.py | 4 +--- src/scorerecalculator/tests.py | 3 +-- src/stats/signals.py | 3 +-- src/stats/tests.py | 3 +-- src/stats/views.py | 3 +-- src/team/tests.py | 4 +--- src/team/views.py | 10 +++------- 23 files changed, 69 insertions(+), 95 deletions(-) diff --git a/src/admin/views.py b/src/admin/views.py index dad575d0..368274eb 100644 --- a/src/admin/views.py +++ b/src/admin/views.py @@ -1,4 +1,5 @@ from django.shortcuts import render + # Create your views here. from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView diff --git a/src/authentication/basic_auth.py b/src/authentication/basic_auth.py index 26dc8428..4ccb9a72 100644 --- a/src/authentication/basic_auth.py +++ b/src/authentication/basic_auth.py @@ -1,10 +1,8 @@ -from django.contrib.auth import (authenticate, get_user_model, - password_validation) +from django.contrib.auth import authenticate, get_user_model, password_validation from rest_framework.exceptions import ValidationError from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED -from authentication.providers import (LoginProvider, RegistrationProvider, - TokenProvider) +from authentication.providers import LoginProvider, RegistrationProvider, TokenProvider from backend.exceptions import FormattedException from backend.signals import login, login_reject diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 61e105a9..80e706e2 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -6,8 +6,7 @@ from django.utils import timezone from rest_framework import serializers from rest_framework.generics import get_object_or_404 -from rest_framework.status import (HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException diff --git a/src/authentication/tests.py b/src/authentication/tests.py index fed564a1..8a258be7 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -3,19 +3,23 @@ import pyotp from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, - HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND) +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND from rest_framework.test import APITestCase -from authentication.models import (BackupCode, InviteCode, PasswordResetToken, - Token, TOTPDevice) -from authentication.views import (AddTwoFactorView, ChangePasswordView, - CreateBotView, DoPasswordResetView, - LoginTwoFactorView, LoginView, - RegenerateBackupCodesView, RegistrationView, - RequestPasswordResetView, VerifyEmailView, - VerifyTwoFactorView) +from authentication.models import BackupCode, InviteCode, PasswordResetToken, Token, TOTPDevice +from authentication.views import ( + AddTwoFactorView, + ChangePasswordView, + CreateBotView, + DoPasswordResetView, + LoginTwoFactorView, + LoginView, + RegenerateBackupCodesView, + RegistrationView, + RequestPasswordResetView, + VerifyEmailView, + VerifyTwoFactorView, +) from config import config from team.models import Team diff --git a/src/authentication/views.py b/src/authentication/views.py index 0de8f1bd..689b1545 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -10,29 +10,26 @@ from django.views.decorators.debug import sensitive_post_parameters from django_filters.rest_framework import DjangoFilterBackend from rest_framework import permissions -from rest_framework.generics import (CreateAPIView, GenericAPIView, - get_object_or_404) -from rest_framework.status import (HTTP_201_CREATED, HTTP_400_BAD_REQUEST, - HTTP_401_UNAUTHORIZED) +from rest_framework.generics import CreateAPIView, GenericAPIView, get_object_or_404 +from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED from rest_framework.views import APIView from authentication import serializers -from authentication.models import (BackupCode, InviteCode, PasswordResetToken, - TOTPDevice) +from authentication.models import BackupCode, InviteCode, PasswordResetToken, TOTPDevice from authentication.permissions import HasTwoFactor, VerifyingTwoFactor -from authentication.serializers import (ChangePasswordSerializer, - CreateBotSerializer, EmailSerializer, - EmailVerificationSerializer, - GenerateInvitesSerializer, - InviteCodeSerializer, - RegistrationSerializer) +from authentication.serializers import ( + ChangePasswordSerializer, + CreateBotSerializer, + EmailSerializer, + EmailVerificationSerializer, + GenerateInvitesSerializer, + InviteCodeSerializer, + RegistrationSerializer, +) from backend.mail import send_email from backend.permissions import IsBot, IsSudo from backend.response import FormattedResponse -from backend.signals import (add_2fa, change_password, email_verified, logout, - password_reset, password_reset_start, - password_reset_start_reject, remove_2fa, - verify_2fa) +from backend.signals import add_2fa, change_password, email_verified, logout, password_reset, password_reset_start, password_reset_start_reject, remove_2fa, verify_2fa from backend.viewsets import AdminListModelViewSet from plugins import providers from team.models import Team diff --git a/src/challenge/models.py b/src/challenge/models.py index b8b9e47b..e329dedb 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -3,8 +3,7 @@ from django.contrib.auth import get_user_model from django.contrib.postgres.indexes import BrinIndex from django.db import models -from django.db.models import (CASCADE, PROTECT, SET_NULL, Case, JSONField, Q, - UniqueConstraint, Value, When) +from django.db.models import CASCADE, PROTECT, SET_NULL, Case, JSONField, Q, UniqueConstraint, Value, When from django.db.models.aggregates import Count from django.db.models.query import Prefetch from django.db.models.signals import post_save diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 070d3969..03009c50 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,10 +1,8 @@ import serpy from rest_framework import serializers -from challenge.models import (Category, Challenge, ChallengeFeedback, File, - Score, Solve, Tag) -from challenge.sql import (get_negative_votes, get_positive_votes, - get_solve_counts) +from challenge.models import Category, Challenge, ChallengeFeedback, File, Score, Solve, Tag +from challenge.sql import get_negative_votes, get_positive_votes, get_solve_counts from hint.serializers import FastHintSerializer diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 5a06fae6..f86b68f3 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -1,9 +1,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.urls import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, - HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from challenge.models import Solve diff --git a/src/challenge/views.py b/src/challenge/views.py index 80979f6b..3223b14c 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -22,19 +22,23 @@ from backend.response import FormattedResponse from backend.signals import flag_reject, flag_score, flag_submit from backend.viewsets import AdminCreateModelViewSet -from challenge.models import (Category, Challenge, ChallengeFeedback, - ChallengeVote, File, Score, Solve, Tag) +from challenge.models import Category, Challenge, ChallengeFeedback, ChallengeVote, File, Score, Solve, Tag from challenge.permissions import CompetitionOpen -from challenge.serializers import (AdminScoreSerializer, - ChallengeFeedbackSerializer, - CreateCategorySerializer, - CreateChallengeSerializer, - FastAdminCategorySerializer, - FastAdminChallengeSerializer, - FastCategorySerializer, - FastChallengeSerializer, FileSerializer, - TagSerializer, get_negative_votes, - get_positive_votes, get_solve_counts) +from challenge.serializers import ( + AdminScoreSerializer, + ChallengeFeedbackSerializer, + CreateCategorySerializer, + CreateChallengeSerializer, + FastAdminCategorySerializer, + FastAdminChallengeSerializer, + FastCategorySerializer, + FastChallengeSerializer, + FileSerializer, + TagSerializer, + get_negative_votes, + get_positive_votes, + get_solve_counts, +) from config import config from hint.models import Hint, HintUse from team.models import Team diff --git a/src/config/tests.py b/src/config/tests.py index ab76de8b..0594149b 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -1,7 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, - HTTP_204_NO_CONTENT, HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from config import config diff --git a/src/config/views.py b/src/config/views.py index e1ebfc42..ab098903 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -1,5 +1,4 @@ -from rest_framework.status import (HTTP_201_CREATED, HTTP_204_NO_CONTENT, - HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN from rest_framework.views import APIView from backend.permissions import AdminOrAnonymousReadOnly diff --git a/src/hint/tests.py b/src/hint/tests.py index c532cdba..041f9c78 100644 --- a/src/hint/tests.py +++ b/src/hint/tests.py @@ -1,6 +1,5 @@ from rest_framework.reverse import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, - HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from challenge.tests.mixins import ChallengeSetupMixin diff --git a/src/hint/views.py b/src/hint/views.py index 992f201a..755e6500 100644 --- a/src/hint/views.py +++ b/src/hint/views.py @@ -12,8 +12,7 @@ from challenge.views import get_cache_key from hint.models import Hint, HintUse from hint.permissions import HasUsedHint -from hint.serializers import (CreateHintSerializer, FullHintSerializer, - HintSerializer, UseHintSerializer) +from hint.serializers import CreateHintSerializer, FullHintSerializer, HintSerializer, UseHintSerializer from team.permissions import HasTeam diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index 9fedde0b..d8569687 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -5,8 +5,7 @@ from challenge.models import Category, Challenge, Score, Solve from config import config -from leaderboard.views import (CTFTimeListView, GraphView, TeamListView, - UserListView) +from leaderboard.views import CTFTimeListView, GraphView, TeamListView, UserListView from team.models import Team diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index 0a11e743..1706b3fd 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -11,18 +11,12 @@ from backend.response import FormattedResponse from challenge.models import Score from config import config -from leaderboard.serializers import (CTFTimeSerializer, - LeaderboardTeamScoreSerializer, - LeaderboardUserScoreSerializer, - MatrixSerializer, TeamPointsSerializer, - UserPointsSerializer) +from leaderboard.serializers import CTFTimeSerializer, LeaderboardTeamScoreSerializer, LeaderboardUserScoreSerializer, MatrixSerializer, TeamPointsSerializer, UserPointsSerializer from team.models import Team def should_hide_scoreboard(): - return not config.get("enable_scoreboard") and ( - config.get("hide_scoreboard_at") == -1 or config.get("hide_scoreboard_at") > time.time() or config.get("end_time") > time.time() - ) + return not config.get("enable_scoreboard") and (config.get("hide_scoreboard_at") == -1 or config.get("hide_scoreboard_at") > time.time() or config.get("end_time") > time.time()) class CTFTimeListView(APIView): diff --git a/src/member/tests.py b/src/member/tests.py index da213ee3..8e626892 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -1,7 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_400_BAD_REQUEST, - HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase diff --git a/src/member/views.py b/src/member/views.py index 3bb9ce56..1d657cab 100644 --- a/src/member/views.py +++ b/src/member/views.py @@ -7,9 +7,7 @@ from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.viewsets import AdminListModelViewSet from member.models import UserIP -from member.serializers import (AdminMemberSerializer, ListMemberSerializer, - MemberSerializer, SelfSerializer, - UserIPSerializer) +from member.serializers import AdminMemberSerializer, ListMemberSerializer, MemberSerializer, SelfSerializer, UserIPSerializer class SelfView(RetrieveUpdateAPIView): diff --git a/src/scorerecalculator/tests.py b/src/scorerecalculator/tests.py index 4fb8fc45..ad72a275 100644 --- a/src/scorerecalculator/tests.py +++ b/src/scorerecalculator/tests.py @@ -2,8 +2,7 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from challenge.models import Score diff --git a/src/stats/signals.py b/src/stats/signals.py index 8721dd66..83d04212 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -3,8 +3,7 @@ from django.dispatch import receiver from prometheus_client import Gauge -from backend.signals import (flag_score, register, team_create, - websocket_connect, websocket_disconnect) +from backend.signals import flag_score, register, team_create, websocket_connect, websocket_disconnect from challenge.models import Solve from member.models import Member from team.models import Team diff --git a/src/stats/tests.py b/src/stats/tests.py index a92138b0..e84fc823 100644 --- a/src/stats/tests.py +++ b/src/stats/tests.py @@ -1,7 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN) +from rest_framework.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from challenge.models import Category, Challenge, Solve diff --git a/src/stats/views.py b/src/stats/views.py index f976c58d..e3d41ed9 100644 --- a/src/stats/views.py +++ b/src/stats/views.py @@ -14,8 +14,7 @@ from challenge.sql import get_incorrect_solve_counts, get_solve_counts from config import config from member.models import UserIP -from stats.signals import (correct_solve_count, member_count, solve_count, - team_count) +from stats.signals import correct_solve_count, member_count, solve_count, team_count from team.models import Team diff --git a/src/team/tests.py b/src/team/tests.py index 014554f0..71bc6756 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -1,8 +1,6 @@ from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import (HTTP_200_OK, HTTP_201_CREATED, - HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, - HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND) +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND from rest_framework.test import APITestCase from challenge.models import Category, Challenge, Solve diff --git a/src/team/views.py b/src/team/views.py index e62e6660..ced8d545 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -1,10 +1,8 @@ from django.http import Http404 from rest_framework import filters -from rest_framework.generics import (CreateAPIView, RetrieveUpdateAPIView, - get_object_or_404) +from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView, get_object_or_404 from rest_framework.permissions import IsAuthenticated -from rest_framework.status import (HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, - HTTP_404_NOT_FOUND) +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND from rest_framework.views import APIView from backend.exceptions import FormattedException @@ -17,9 +15,7 @@ from member.models import Member from team.models import Team from team.permissions import HasTeam, IsTeamOwnerOrReadOnly, TeamsEnabled -from team.serializers import (AdminTeamSerializer, CreateTeamSerializer, - ListTeamSerializer, SelfTeamSerializer, - TeamSerializer) +from team.serializers import AdminTeamSerializer, CreateTeamSerializer, ListTeamSerializer, SelfTeamSerializer, TeamSerializer class SelfView(RetrieveUpdateAPIView): From 0f00f9ee6dc1b3770ef85d7690c271fa5fa936b6 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:54:20 +0100 Subject: [PATCH 072/185] Use 'black' profile for isort --- src/authentication/serializers.py | 6 +++++- src/authentication/tests.py | 17 +++++++++++++++-- src/authentication/views.py | 18 ++++++++++++++++-- src/challenge/models.py | 12 +++++++++++- src/challenge/serializers.py | 10 +++++++++- src/challenge/tests/test_views.py | 8 +++++++- src/challenge/views.py | 11 ++++++++++- src/config/tests.py | 7 ++++++- src/config/views.py | 7 ++++++- src/hint/views.py | 7 ++++++- src/leaderboard/views.py | 9 ++++++++- src/member/tests.py | 7 ++++++- src/member/views.py | 8 +++++++- src/stats/signals.py | 8 +++++++- src/team/tests.py | 9 ++++++++- src/team/views.py | 20 +++++++++++++++++--- 16 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 80e706e2..184f55f0 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -6,7 +6,11 @@ from django.utils import timezone from rest_framework import serializers from rest_framework.generics import get_object_or_404 -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, +) from authentication.models import InviteCode, PasswordResetToken from backend.exceptions import FormattedException diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 8a258be7..e5c74739 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -3,10 +3,23 @@ import pyotp from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND +from rest_framework.status import ( + HTTP_200_OK, + HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, + HTTP_404_NOT_FOUND, +) from rest_framework.test import APITestCase -from authentication.models import BackupCode, InviteCode, PasswordResetToken, Token, TOTPDevice +from authentication.models import ( + BackupCode, + InviteCode, + PasswordResetToken, + Token, + TOTPDevice, +) from authentication.views import ( AddTwoFactorView, ChangePasswordView, diff --git a/src/authentication/views.py b/src/authentication/views.py index 689b1545..f633f5bc 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -11,7 +11,11 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import permissions from rest_framework.generics import CreateAPIView, GenericAPIView, get_object_or_404 -from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED +from rest_framework.status import ( + HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, +) from rest_framework.views import APIView from authentication import serializers @@ -29,7 +33,17 @@ from backend.mail import send_email from backend.permissions import IsBot, IsSudo from backend.response import FormattedResponse -from backend.signals import add_2fa, change_password, email_verified, logout, password_reset, password_reset_start, password_reset_start_reject, remove_2fa, verify_2fa +from backend.signals import ( + add_2fa, + change_password, + email_verified, + logout, + password_reset, + password_reset_start, + password_reset_start_reject, + remove_2fa, + verify_2fa, +) from backend.viewsets import AdminListModelViewSet from plugins import providers from team.models import Team diff --git a/src/challenge/models.py b/src/challenge/models.py index e329dedb..c7e8ba05 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -3,7 +3,17 @@ from django.contrib.auth import get_user_model from django.contrib.postgres.indexes import BrinIndex from django.db import models -from django.db.models import CASCADE, PROTECT, SET_NULL, Case, JSONField, Q, UniqueConstraint, Value, When +from django.db.models import ( + CASCADE, + PROTECT, + SET_NULL, + Case, + JSONField, + Q, + UniqueConstraint, + Value, + When, +) from django.db.models.aggregates import Count from django.db.models.query import Prefetch from django.db.models.signals import post_save diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 03009c50..c3a6de2f 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -1,7 +1,15 @@ import serpy from rest_framework import serializers -from challenge.models import Category, Challenge, ChallengeFeedback, File, Score, Solve, Tag +from challenge.models import ( + Category, + Challenge, + ChallengeFeedback, + File, + Score, + Solve, + Tag, +) from challenge.sql import get_negative_votes, get_positive_votes, get_solve_counts from hint.serializers import FastHintSerializer diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index f86b68f3..cc94f1d7 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -1,7 +1,13 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.urls import reverse -from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_200_OK, + HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, +) from rest_framework.test import APITestCase from challenge.models import Solve diff --git a/src/challenge/views.py b/src/challenge/views.py index 3223b14c..520939b6 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -22,7 +22,16 @@ from backend.response import FormattedResponse from backend.signals import flag_reject, flag_score, flag_submit from backend.viewsets import AdminCreateModelViewSet -from challenge.models import Category, Challenge, ChallengeFeedback, ChallengeVote, File, Score, Solve, Tag +from challenge.models import ( + Category, + Challenge, + ChallengeFeedback, + ChallengeVote, + File, + Score, + Solve, + Tag, +) from challenge.permissions import CompetitionOpen from challenge.serializers import ( AdminScoreSerializer, diff --git a/src/config/tests.py b/src/config/tests.py index 0594149b..1ce760f4 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -1,6 +1,11 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_200_OK, + HTTP_201_CREATED, + HTTP_204_NO_CONTENT, + HTTP_403_FORBIDDEN, +) from rest_framework.test import APITestCase from config import config diff --git a/src/config/views.py b/src/config/views.py index ab098903..b40e4a15 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -1,4 +1,9 @@ -from rest_framework.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_201_CREATED, + HTTP_204_NO_CONTENT, + HTTP_400_BAD_REQUEST, + HTTP_403_FORBIDDEN, +) from rest_framework.views import APIView from backend.permissions import AdminOrAnonymousReadOnly diff --git a/src/hint/views.py b/src/hint/views.py index 755e6500..0e9db09a 100644 --- a/src/hint/views.py +++ b/src/hint/views.py @@ -12,7 +12,12 @@ from challenge.views import get_cache_key from hint.models import Hint, HintUse from hint.permissions import HasUsedHint -from hint.serializers import CreateHintSerializer, FullHintSerializer, HintSerializer, UseHintSerializer +from hint.serializers import ( + CreateHintSerializer, + FullHintSerializer, + HintSerializer, + UseHintSerializer, +) from team.permissions import HasTeam diff --git a/src/leaderboard/views.py b/src/leaderboard/views.py index 1706b3fd..de758055 100644 --- a/src/leaderboard/views.py +++ b/src/leaderboard/views.py @@ -11,7 +11,14 @@ from backend.response import FormattedResponse from challenge.models import Score from config import config -from leaderboard.serializers import CTFTimeSerializer, LeaderboardTeamScoreSerializer, LeaderboardUserScoreSerializer, MatrixSerializer, TeamPointsSerializer, UserPointsSerializer +from leaderboard.serializers import ( + CTFTimeSerializer, + LeaderboardTeamScoreSerializer, + LeaderboardUserScoreSerializer, + MatrixSerializer, + TeamPointsSerializer, + UserPointsSerializer, +) from team.models import Team diff --git a/src/member/tests.py b/src/member/tests.py index 8e626892..772f6b0d 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -1,6 +1,11 @@ from django.contrib.auth import get_user_model from rest_framework.reverse import reverse -from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_200_OK, + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, +) from rest_framework.test import APITestCase diff --git a/src/member/views.py b/src/member/views.py index 1d657cab..07f145d0 100644 --- a/src/member/views.py +++ b/src/member/views.py @@ -7,7 +7,13 @@ from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot from backend.viewsets import AdminListModelViewSet from member.models import UserIP -from member.serializers import AdminMemberSerializer, ListMemberSerializer, MemberSerializer, SelfSerializer, UserIPSerializer +from member.serializers import ( + AdminMemberSerializer, + ListMemberSerializer, + MemberSerializer, + SelfSerializer, + UserIPSerializer, +) class SelfView(RetrieveUpdateAPIView): diff --git a/src/stats/signals.py b/src/stats/signals.py index 83d04212..b3d68cef 100644 --- a/src/stats/signals.py +++ b/src/stats/signals.py @@ -3,7 +3,13 @@ from django.dispatch import receiver from prometheus_client import Gauge -from backend.signals import flag_score, register, team_create, websocket_connect, websocket_disconnect +from backend.signals import ( + flag_score, + register, + team_create, + websocket_connect, + websocket_disconnect, +) from challenge.models import Solve from member.models import Member from team.models import Team diff --git a/src/team/tests.py b/src/team/tests.py index 71bc6756..2705d0c4 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -1,6 +1,13 @@ from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND +from rest_framework.status import ( + HTTP_200_OK, + HTTP_201_CREATED, + HTTP_400_BAD_REQUEST, + HTTP_401_UNAUTHORIZED, + HTTP_403_FORBIDDEN, + HTTP_404_NOT_FOUND, +) from rest_framework.test import APITestCase from challenge.models import Category, Challenge, Solve diff --git a/src/team/views.py b/src/team/views.py index ced8d545..f569d7af 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -1,8 +1,16 @@ from django.http import Http404 from rest_framework import filters -from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView, get_object_or_404 +from rest_framework.generics import ( + CreateAPIView, + RetrieveUpdateAPIView, + get_object_or_404, +) from rest_framework.permissions import IsAuthenticated -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND +from rest_framework.status import ( + HTTP_400_BAD_REQUEST, + HTTP_403_FORBIDDEN, + HTTP_404_NOT_FOUND, +) from rest_framework.views import APIView from backend.exceptions import FormattedException @@ -15,7 +23,13 @@ from member.models import Member from team.models import Team from team.permissions import HasTeam, IsTeamOwnerOrReadOnly, TeamsEnabled -from team.serializers import AdminTeamSerializer, CreateTeamSerializer, ListTeamSerializer, SelfTeamSerializer, TeamSerializer +from team.serializers import ( + AdminTeamSerializer, + CreateTeamSerializer, + ListTeamSerializer, + SelfTeamSerializer, + TeamSerializer, +) class SelfView(RetrieveUpdateAPIView): From 51b1f74c9127477c421546d40711cc084fbe34fa Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:55:05 +0100 Subject: [PATCH 073/185] Add isort configuration to pyproject.toml --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 07ee6fa4..3b395b48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,10 @@ python_files = "tests.py test_*.py *_tests.py" exclude = 'migrations' line_length = 200 +[tool.isort] +profile = "black" +multi_line_output = 3 + [build-system] requires = ["poetry-core>=1.0.0a5"] build-backend = "poetry.core.masonry.api" From c0fbdeff234444b914cffdf0aabcacd34cfff278 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:55:14 +0100 Subject: [PATCH 074/185] Add Makefile --- Makefile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..01b79518 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +test: + cd src && \ + DJANGO_SETTINGS_MODULE='backend.settings.lint' \ + python manage.py migrate && \ + pytest --cov=. --cov-report=xml + +format: + isort -rc src && \ + black src + +lint: + flake8 && \ + isort --check-only src From 2c9f1f2b7fc1372688dc4de7c6aef08216019098 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:56:39 +0100 Subject: [PATCH 075/185] Replace flake8 with flake9 --- poetry.lock | 38 +++++++++++++++++++------------------- pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index a4ff10a1..613c6895 100644 --- a/poetry.lock +++ b/poetry.lock @@ -120,14 +120,14 @@ visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] name = "autopep8" -version = "1.5.7" +version = "1.5.5" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "main" optional = false python-versions = "*" [package.dependencies] -pycodestyle = ">=2.7.0" +pycodestyle = ">=2.6.0" toml = "*" [[package]] @@ -564,17 +564,17 @@ optional = false python-versions = "*" [[package]] -name = "flake8" -version = "3.9.2" +name = "flake9" +version = "3.8.3.post2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.4" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" [[package]] name = "gprof2dot" @@ -994,7 +994,7 @@ pyasn1 = ">=0.4.6,<0.5.0" [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.6.0" description = "Python style guide checker" category = "main" optional = false @@ -1010,7 +1010,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" -version = "2.3.1" +version = "2.2.0" description = "passive checker of Python programs" category = "dev" optional = false @@ -1516,7 +1516,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e5bc834af15dccf632582ad50d6ee8ec15df2264274d146446c880acd26ac57b" +content-hash = "97995bc62298eb11d1a51b9decebfbfdace3105a123853cee64d23ac0bee284f" [metadata.files] aioredis = [ @@ -1559,8 +1559,8 @@ automat = [ {file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"}, ] autopep8 = [ - {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"}, - {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, + {file = "autopep8-1.5.5-py2.py3-none-any.whl", hash = "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea"}, + {file = "autopep8-1.5.5.tar.gz", hash = "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, @@ -1795,9 +1795,9 @@ filelock = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] -flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +flake9 = [ + {file = "flake9-3.8.3.post2-py3-none-any.whl", hash = "sha256:47dced969a802a8892740bcaa35ae07232709b2ade803c45f48dd03ccb7f825f"}, + {file = "flake9-3.8.3.post2.tar.gz", hash = "sha256:daefdbfb3d320eb215a4a52c62a4b4a027cbe11d39f5dab30df908b40fce5ba7"}, ] gprof2dot = [ {file = "gprof2dot-2021.2.21.tar.gz", hash = "sha256:1223189383b53dcc8ecfd45787ac48c0ed7b4dbc16ee8b88695d053eea1acabf"}, @@ -2144,16 +2144,16 @@ pyasn1-modules = [ {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, diff --git a/pyproject.toml b/pyproject.toml index 3b395b48..26e20b89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,8 +40,8 @@ pytest = "^6.2.4" pytest-cov = "^2.12.0" pytest-django = "^4.3.0" isort = "^5.8.0" -flake8 = "^3.9.2" pre-commit = "^2.13.0" +flake9 = "^3.8.3" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "backend.settings.local" From 6650e2715340e73f479fd6dd73f6216e5291d61d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 21:57:12 +0100 Subject: [PATCH 076/185] Add flake9 configuration to pyproject.toml --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 26e20b89..694fd6f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,11 @@ line_length = 200 profile = "black" multi_line_output = 3 +[tool.flake8] +max-line-length = 200 +max-complexity = 25 +hang-closing = true + [build-system] requires = ["poetry-core>=1.0.0a5"] build-backend = "poetry.core.masonry.api" From 831cf1ef5d8c2879859664bb3dd774352cce0472 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:00:46 +0100 Subject: [PATCH 077/185] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 694fd6f3..c9f22476 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,9 +56,9 @@ profile = "black" multi_line_output = 3 [tool.flake8] +exclude = "*migrations*,*settings*" max-line-length = 200 max-complexity = 25 -hang-closing = true [build-system] requires = ["poetry-core>=1.0.0a5"] From 0853530e1d116196098936a99c2f27513a93f3ce Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:03:36 +0100 Subject: [PATCH 078/185] Use importlib for weird imports --- src/challenge/apps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/challenge/apps.py b/src/challenge/apps.py index 5f0a0984..a6d91f86 100644 --- a/src/challenge/apps.py +++ b/src/challenge/apps.py @@ -1,3 +1,5 @@ +from importlib import import_module + from django.apps import AppConfig @@ -5,5 +7,4 @@ class ChallengeConfig(AppConfig): name = "challenge" def ready(self): - # noinspection PyUnresolvedReferences - import challenge.signals + import_module("challenge.signals", "challenge") From 4570e999b3a0adbca9dd1c5de4a37a71d18970c6 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:04:19 +0100 Subject: [PATCH 079/185] Remove redundant assignment --- src/challenge/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/challenge/models.py b/src/challenge/models.py index c7e8ba05..dce32736 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -192,8 +192,7 @@ class ChallengeFeedback(ExportModelOperationsMixin("challenge_feedback"), models @receiver(post_save, sender=Challenge) def on_challenge_update(sender, instance, created, **kwargs): - if not created: - new_score = instance.score + ... class Score(ExportModelOperationsMixin("score"), models.Model): From 3aeeff36a4b48b2ecb5c70c18b3187570ea6bcc5 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:04:43 +0100 Subject: [PATCH 080/185] Remove redundant assignment --- src/ractf/management/commands/insert_mega_dummy_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ractf/management/commands/insert_mega_dummy_data.py b/src/ractf/management/commands/insert_mega_dummy_data.py index 803508ed..129732b1 100644 --- a/src/ractf/management/commands/insert_mega_dummy_data.py +++ b/src/ractf/management/commands/insert_mega_dummy_data.py @@ -105,7 +105,6 @@ def random_rpn_op(depth=0): z = team.members.all() user = z[0] user2 = z[1] - used = [] for j in range(50): points = random.randint(0, 999) score = Score(team=team, reason="challenge", points=points, penalty=0, leaderboard=True) From b9a888d28699d2d9dd31ed989087ee15782999b9 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:07:36 +0100 Subject: [PATCH 081/185] Remove import at bottom of file in stats/apps.py --- src/sockets/apps.py | 5 +++-- src/stats/apps.py | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/sockets/apps.py b/src/sockets/apps.py index d65b83be..9d648930 100644 --- a/src/sockets/apps.py +++ b/src/sockets/apps.py @@ -1,3 +1,5 @@ +from importlib import import_module + from django.apps import AppConfig @@ -5,5 +7,4 @@ class SocketsConfig(AppConfig): name = "sockets" def ready(self): - # noinspection PyUnresolvedReferences - import sockets.signals + import_module("sockets.signals", "sockets") diff --git a/src/stats/apps.py b/src/stats/apps.py index ec955afc..6fd8f89a 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -1,10 +1,10 @@ import sys -from importlib import import_module from django.apps import AppConfig import challenge import member +import stats import team @@ -18,11 +18,9 @@ def ready(self): # Don't run stats-related logic if we haven't migrated yet return - from . import signals - Team, Solve, Member = team.models.Team, challenge.models.Solve, member.models.Member - signals.team_count.set(Team.objects.count()) - signals.solve_count.set(Solve.objects.count()) - signals.member_count.set(Member.objects.count()) - signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) + stats.signals.team_count.set(Team.objects.count()) + stats.signals.solve_count.set(Solve.objects.count()) + stats.signals.member_count.set(Member.objects.count()) + stats.signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) From 7592d101bc2f8d75a611f1c55ab8412720329e7d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:07:55 +0100 Subject: [PATCH 082/185] Remove sockets/models.py --- src/sockets/models.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/sockets/models.py diff --git a/src/sockets/models.py b/src/sockets/models.py deleted file mode 100644 index 71a83623..00000000 --- a/src/sockets/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. From d4dd3f400f122df3032141d88628bed40c87e01b Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:10:53 +0100 Subject: [PATCH 083/185] Remove redundant re-imports of config.config --- src/authentication/tests.py | 4 ++-- src/member/models.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index e5c74739..3b5e4df0 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -123,7 +123,7 @@ def test_register_admin(self): "password": "uO7*$E@0ngqL", "email": "user6@example.org", } - response = self.client.post(reverse("register"), data) + self.client.post(reverse("register"), data) self.assertTrue(get_user_model().objects.filter(username=data["username"]).first().is_staff) def test_register_second(self): @@ -138,7 +138,7 @@ def test_register_second(self): "password": "uO7*$E@0ngqL", "email": "user7@example.org", } - response = self.client.post(reverse("register"), data) + self.client.post(reverse("register"), data) self.assertFalse(get_user_model().objects.filter(username=data["username"]).first().is_staff) def test_register_malformed(self): diff --git a/src/member/models.py b/src/member/models.py index bb1c7061..9aa9fb01 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -54,8 +54,6 @@ def __str__(self): return self.username def can_login(self): - from config import config - return self.is_staff or (config.get("enable_login") and (config.get("enable_prelogin") or config.get("start_time") <= time.time())) def issue_token(self, owner=None): @@ -69,8 +67,6 @@ def has_2fa(self): return hasattr(self, "totp_device") and self.totp_device.verified def should_deny_admin(self): - from config import config - return config.get("enable_force_admin_2fa") and not self.has_2fa() From 20b52cf1b2a19e44830fb2387efdee705d3bb6ed Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:11:38 +0100 Subject: [PATCH 084/185] Remove redundant assignments of 'response' --- src/authentication/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 3b5e4df0..7173b7a2 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -579,7 +579,7 @@ def test_remove_2fa_removes_2fa(self): totp_device.verified = True totp_device.save() self.client.force_authenticate(user=get_user_model().objects.get(id=self.user.id)) - response = self.client.post(reverse("remove-2fa"), data={"otp": pyotp.TOTP(totp_device.totp_secret).now()}) + self.client.post(reverse("remove-2fa"), data={"otp": pyotp.TOTP(totp_device.totp_secret).now()}) self.assertFalse(get_user_model().objects.get(id=self.user.id).has_2fa()) def test_remove_2fa_no_2fa(self): From db478eb07b5f88361771d30883486b18b4a3846e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:11:57 +0100 Subject: [PATCH 085/185] Remove admin/admin.py --- src/admin/admin.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/admin/admin.py diff --git a/src/admin/admin.py b/src/admin/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/src/admin/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. From 6125027ab60e98162e8de0a81824b97598158e2e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:12:28 +0100 Subject: [PATCH 086/185] Remove unused 'render' import --- src/admin/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/admin/views.py b/src/admin/views.py index 368274eb..cc88003d 100644 --- a/src/admin/views.py +++ b/src/admin/views.py @@ -1,6 +1,3 @@ -from django.shortcuts import render - -# Create your views here. from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView From a49bb387f17b44d828e148032b9c34f9f129c008 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:13:10 +0100 Subject: [PATCH 087/185] Remove redundant assignments of 'response' --- src/authentication/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 7173b7a2..9742fad0 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -303,7 +303,7 @@ def test_register_invite_required_auto_team(self): "email": "user12@example.com", "invite": "test4", } - response = self.client.post(reverse("register"), data) + self.client.post(reverse("register"), data) self.assertEqual(get_user_model().objects.get(username="user12").team.id, self.team.id) From 132c0fbf7c9c76b98036218efce8fb079dea5e1a Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:13:34 +0100 Subject: [PATCH 088/185] Remove redundant assignments of 'response' --- src/member/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/member/tests.py b/src/member/tests.py index 772f6b0d..4bad414a 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -43,7 +43,7 @@ def test_self_change_email_token_change(self): pr_token = self.user.password_reset_token ev_token = self.user.email_token self.client.force_authenticate(self.user) - response = self.client.put(reverse("member-self"), data={"email": "test-self3@example.org"}) + self.client.put(reverse("member-self"), data={"email": "test-self3@example.org"}) user = get_user_model().objects.get(id=self.user.id) self.assertNotEqual(pr_token, user.password_reset_token) self.assertNotEqual(ev_token, user.email_token) From 6221709c8a2ec512c39175b2b6f12990b29db6ff Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:15:29 +0100 Subject: [PATCH 089/185] Replace bare except with 'except SMTPException' --- src/authentication/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 184f55f0..37709157 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -1,5 +1,6 @@ import secrets import time +from smtplib import SMTPException from django.conf import settings from django.contrib.auth import get_user_model, password_validation @@ -78,7 +79,7 @@ def create(self, validated_data): user.save() try: send_email(user.email, "RACTF - Verify your email", "verify", url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token)) - except: + except SMTPException: user.delete() raise FormattedException(m="creation_failed") From 49ab4641e49b5e9f06660fa7cee9d77a9ebd7460 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:19:10 +0100 Subject: [PATCH 090/185] Use file-based cache for linting --- src/backend/settings/lint.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py index e0143b98..385e23af 100644 --- a/src/backend/settings/lint.py +++ b/src/backend/settings/lint.py @@ -13,7 +13,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "/tmp/db.sqlite3", + "NAME": "/tmp/ractf-linting.db", "USER": "", "PASSWORD": "", "HOST": "", @@ -27,7 +27,8 @@ CACHES = { "default": { - "BACKEND": "django.core.cache.backends.locmem.LocMemCache", - "LOCATION": "dead-beef", - } + "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", + "LOCATION": "/tmp/ractf-linting.db", + "OPTIONS": {"MAX_ENTRIES": 1000}, + }, } From c305d66e364ecb6446fb3e72751d27286c284707 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:19:48 +0100 Subject: [PATCH 091/185] Update cache file name --- src/backend/settings/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py index 385e23af..f9ef3a3c 100644 --- a/src/backend/settings/lint.py +++ b/src/backend/settings/lint.py @@ -28,7 +28,7 @@ CACHES = { "default": { "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", - "LOCATION": "/tmp/ractf-linting.db", + "LOCATION": "/tmp/ractf-linting.cache", "OPTIONS": {"MAX_ENTRIES": 1000}, }, } From 24392a566a6d2b9d847e35f81422ef970a1b44e0 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:21:06 +0100 Subject: [PATCH 092/185] Update lint.py --- src/backend/settings/lint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py index f9ef3a3c..965d417b 100644 --- a/src/backend/settings/lint.py +++ b/src/backend/settings/lint.py @@ -30,5 +30,6 @@ "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", "LOCATION": "/tmp/ractf-linting.cache", "OPTIONS": {"MAX_ENTRIES": 1000}, + "TIMEOUT": 60, }, } From 46dab0d52d89fdbedd9d2e7e0a7f42986b664240 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:31:12 +0100 Subject: [PATCH 093/185] Add better-exceptions to dev dependencies --- poetry.lock | 18 +++++++++++++++++- pyproject.toml | 1 + src/admin/apps.py | 5 +++++ src/backend/settings/__init__.py | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 613c6895..b3fcabf2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -138,6 +138,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "better-exceptions" +version = "0.3.3" +description = "Pretty and helpful exceptions, automatically" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + [[package]] name = "black" version = "20.8b1" @@ -1516,7 +1527,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "97995bc62298eb11d1a51b9decebfbfdace3105a123853cee64d23ac0bee284f" +content-hash = "6a74360f0112f580354789161fce49b5b578f21e6e9064d3156e19d0662c9b30" [metadata.files] aioredis = [ @@ -1566,6 +1577,11 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] +better-exceptions = [ + {file = "better_exceptions-0.3.3-py3-none-any.whl", hash = "sha256:9c70b1c61d5a179b84cd2c9d62c3324b667d74286207343645ed4306fdaad976"}, + {file = "better_exceptions-0.3.3-py3.8.egg", hash = "sha256:bf111d0c9994ac1123f29c24907362bed2320a86809c85f0d858396000667ce2"}, + {file = "better_exceptions-0.3.3.tar.gz", hash = "sha256:e4e6bc18444d5f04e6e894b10381e5e921d3d544240418162c7db57e9eb3453b"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] diff --git a/pyproject.toml b/pyproject.toml index c9f22476..0dba6e9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ pytest-django = "^4.3.0" isort = "^5.8.0" pre-commit = "^2.13.0" flake9 = "^3.8.3" +better-exceptions = "^0.3.3" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "backend.settings.local" diff --git a/src/admin/apps.py b/src/admin/apps.py index 86bb5bc7..043072c1 100644 --- a/src/admin/apps.py +++ b/src/admin/apps.py @@ -1,5 +1,10 @@ from django.apps import AppConfig +from config import config + class AdminConfig(AppConfig): name = "admin" + + def ready(self): + config.load() diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index eab45265..b80da22e 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -131,6 +131,8 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "django_prometheus.middleware.PrometheusAfterMiddleware", ] +if DEBUG: + MIDDLEWARE.insert(0, "better_exceptions.integrations.django.BetterExceptionsMiddleware") if os.getenv("ENABLE_SILK"): INSTALLED_APPS.insert(len(INSTALLED_APPS) - 6, "silk") From 87c7560fcc7c3b307eb7f3db65747e7ad18c50fd Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:37:14 +0100 Subject: [PATCH 094/185] Update Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 01b79518..2bd27b33 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ test: cd src && \ DJANGO_SETTINGS_MODULE='backend.settings.lint' \ + BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ pytest --cov=. --cov-report=xml From 2dc5ffe72d732a25538769438bad75bbdbde179d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:42:21 +0100 Subject: [PATCH 095/185] Use CachedBackend for local linting --- src/backend/settings/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py index 965d417b..af7ea3ec 100644 --- a/src/backend/settings/lint.py +++ b/src/backend/settings/lint.py @@ -22,7 +22,7 @@ } CONFIG = { - "BACKEND": "config.backends.DatabaseBackend", + "BACKEND": "config.backends.CachedBackend", } CACHES = { From f3a541450bc3d8373ce67238adbb3e60249da33b Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:43:33 +0100 Subject: [PATCH 096/185] Update exceptions to catch for migration checking --- src/config/backends.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/backends.py b/src/config/backends.py index b9a4a0d8..311519f0 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -2,7 +2,7 @@ from django.core.cache import caches from django.db.models.query import QuerySet -from django.db.utils import ProgrammingError +from django.db.utils import OperationalError, ProgrammingError from config.models import Config @@ -82,7 +82,7 @@ def load(self, defaults): config_exists, migrations_needed = False, False try: config_exists = db_config.exists() - except ProgrammingError: + except ProgrammingError, OperationalError: migrations_needed = True if config_exists: From 7f3da8365fac759a56d62815ca5c9e110c8747da Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:43:51 +0100 Subject: [PATCH 097/185] Update exceptions to catch for migration checking --- src/config/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/backends.py b/src/config/backends.py index 311519f0..b39a0900 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -82,7 +82,7 @@ def load(self, defaults): config_exists, migrations_needed = False, False try: config_exists = db_config.exists() - except ProgrammingError, OperationalError: + except (ProgrammingError, OperationalError): migrations_needed = True if config_exists: From efc13d2251eafa77abe09bf6d78bab9c2e00225d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:50:11 +0100 Subject: [PATCH 098/185] Replace test checker bodge in migrations with database check --- .../migrations/0002_auto_20200808_1337.py | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/challenge/migrations/0002_auto_20200808_1337.py b/src/challenge/migrations/0002_auto_20200808_1337.py index 3829d692..e9a5d74c 100644 --- a/src/challenge/migrations/0002_auto_20200808_1337.py +++ b/src/challenge/migrations/0002_auto_20200808_1337.py @@ -14,68 +14,70 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('challenge', '0001_initial'), - ('team', '0001_initial'), + ("challenge", "0001_initial"), + ("team", "0001_initial"), ] operations = [ migrations.AddField( - model_name='solve', - name='solved_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='solves', to=settings.AUTH_USER_MODEL), + model_name="solve", + name="solved_by", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="solves", to=settings.AUTH_USER_MODEL), ), migrations.AddField( - model_name='solve', - name='team', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='solves', to='team.Team'), + model_name="solve", + name="team", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name="solves", to="team.Team"), ), migrations.AddField( - model_name='score', - name='team', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scores', to='team.Team'), + model_name="score", + name="team", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name="scores", to="team.Team"), ), migrations.AddField( - model_name='score', - name='user', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='scores', to=settings.AUTH_USER_MODEL), + model_name="score", + name="user", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="scores", to=settings.AUTH_USER_MODEL), ), migrations.AddField( - model_name='file', - name='challenge', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='file_set', to='challenge.Challenge'), + model_name="file", + name="challenge", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="file_set", to="challenge.Challenge"), ), migrations.AddField( - model_name='challenge', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='category_challenges', to='challenge.Category'), + model_name="challenge", + name="category", + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="category_challenges", to="challenge.Category"), ), migrations.AddField( - model_name='challenge', - name='first_blood', - field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='first_bloods', to=settings.AUTH_USER_MODEL), + model_name="challenge", + name="first_blood", + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="first_bloods", to=settings.AUTH_USER_MODEL), ), migrations.AddField( - model_name='challenge', - name='unlocks', - field=models.ManyToManyField(blank=True, related_name='unlocked_by', to='challenge.Challenge'), + model_name="challenge", + name="unlocks", + field=models.ManyToManyField(blank=True, related_name="unlocked_by", to="challenge.Challenge"), ), ] - if not os.getenv("DJANGO_SETTINGS_MODULE", "").endswith("test"): + if not settings.DATABASES.get("default", {}).get("ENGINE", "").endswith("postgresql"): operations.append( migrations.AddIndex( - model_name='solve', - index=django.contrib.postgres.indexes.BrinIndex(autosummarize=True, fields=['challenge'], name='challenge_s_challen_dd8715_brin'), + model_name="solve", + index=django.contrib.postgres.indexes.BrinIndex(autosummarize=True, fields=["challenge"], name="challenge_s_challen_dd8715_brin"), ) ) - operations.extend([ - migrations.AddConstraint( - model_name='solve', - constraint=models.UniqueConstraint(condition=models.Q(('correct', True), ('team__isnull', False)), fields=('team', 'challenge'), name='unique_team_challenge_correct'), - ), - migrations.AddConstraint( - model_name='solve', - constraint=models.UniqueConstraint(condition=models.Q(correct=True), fields=('solved_by', 'challenge'), name='unique_member_challenge_correct'), - ), - ]) + operations.extend( + [ + migrations.AddConstraint( + model_name="solve", + constraint=models.UniqueConstraint(condition=models.Q(("correct", True), ("team__isnull", False)), fields=("team", "challenge"), name="unique_team_challenge_correct"), + ), + migrations.AddConstraint( + model_name="solve", + constraint=models.UniqueConstraint(condition=models.Q(correct=True), fields=("solved_by", "challenge"), name="unique_member_challenge_correct"), + ), + ] + ) From 4cafe14ca9be2b3a355b7079e003293d427bec05 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:51:21 +0100 Subject: [PATCH 099/185] Replace test checker bodge in migrations with database check --- src/challenge/migrations/0002_auto_20200808_1337.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/challenge/migrations/0002_auto_20200808_1337.py b/src/challenge/migrations/0002_auto_20200808_1337.py index e9a5d74c..847a3824 100644 --- a/src/challenge/migrations/0002_auto_20200808_1337.py +++ b/src/challenge/migrations/0002_auto_20200808_1337.py @@ -61,7 +61,7 @@ class Migration(migrations.Migration): ), ] - if not settings.DATABASES.get("default", {}).get("ENGINE", "").endswith("postgresql"): + if settings.DATABASES.get("default", {}).get("ENGINE", "").endswith("postgresql"): operations.append( migrations.AddIndex( model_name="solve", From 2860c76144f12185f3f83e3202f5410dbcf9126e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:52:13 +0100 Subject: [PATCH 100/185] export DJANGO_SETTINGS_MODULE in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2bd27b33..f07642d2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ test: + export DJANGO_SETTINGS_MODULE='backend.settings.lint' && \ cd src && \ - DJANGO_SETTINGS_MODULE='backend.settings.lint' \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ pytest --cov=. --cov-report=xml From 765381838262725e294429a4aa2550e83291e019 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 22:55:37 +0100 Subject: [PATCH 101/185] Use importlib to import stats.signals --- src/stats/apps.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/stats/apps.py b/src/stats/apps.py index 6fd8f89a..e60fca45 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -1,10 +1,10 @@ import sys +from importlib import import_module from django.apps import AppConfig import challenge import member -import stats import team @@ -18,9 +18,11 @@ def ready(self): # Don't run stats-related logic if we haven't migrated yet return + signals = import_module("stats.signals", "stats") + Team, Solve, Member = team.models.Team, challenge.models.Solve, member.models.Member - stats.signals.team_count.set(Team.objects.count()) - stats.signals.solve_count.set(Solve.objects.count()) - stats.signals.member_count.set(Member.objects.count()) - stats.signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) + signals.team_count.set(Team.objects.count()) + signals.solve_count.set(Solve.objects.count()) + signals.member_count.set(Member.objects.count()) + signals.correct_solve_count.set(Solve.objects.filter(correct=True).count()) From fdc126ac4810279882a6f90b6530dff815e289cb Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 23:04:32 +0100 Subject: [PATCH 102/185] Remove test requirement on precommit :( --- .pre-commit-config.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82aebde7..3b394a06 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,10 +17,3 @@ repos: entry: poetry run make lint language: system types: [python] - - - id: poetry-test - name: Poetry Test - description: This hook runs `make test` inside a relevant poetry environment. - entry: poetry run make test - language: system - types: [python] From 28094b72105747ac3b0d680177d2732b241df99a Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 23:05:57 +0100 Subject: [PATCH 103/185] Update pytest config in pyproject.toml --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0dba6e9f..736b4708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,6 @@ flake9 = "^3.8.3" better-exceptions = "^0.3.3" [tool.pytest.ini_options] -DJANGO_SETTINGS_MODULE = "backend.settings.local" python_files = "tests.py test_*.py *_tests.py" [tool.black] From d9f1b8d991fdbed91878d6c453acfb6cde1a89ab Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 23:17:49 +0100 Subject: [PATCH 104/185] Update CHANNEL_LAYERS in lint settings --- src/backend/settings/lint.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/settings/lint.py b/src/backend/settings/lint.py index af7ea3ec..fd9e5ca3 100644 --- a/src/backend/settings/lint.py +++ b/src/backend/settings/lint.py @@ -25,6 +25,12 @@ "BACKEND": "config.backends.CachedBackend", } +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels.layers.InMemoryChannelLayer", + } +} + CACHES = { "default": { "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", From 8efab7cb2efd59a1d9f90559681443a372a627c8 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 23:25:39 +0100 Subject: [PATCH 105/185] Add 'make format' precommit hook --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b394a06..9dd914b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,3 +17,10 @@ repos: entry: poetry run make lint language: system types: [python] + + - id: poetry-format + name: Poetry Format + description: This hook runs `make format` inside a relevant poetry environment. + entry: poetry run make format + language: system + types: [python] From d8042bb6c952e9fd8cd826700faa6122f8daccf5 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Mon, 7 Jun 2021 23:31:31 +0100 Subject: [PATCH 106/185] Update Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f07642d2..f37d9782 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,5 @@ format: lint: flake8 && \ + pre-commit run && \ isort --check-only src From b96ba4a25d743781515a6fc4677c4c5d405b023d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 00:30:52 +0100 Subject: [PATCH 107/185] Add DEFAULT_AUTO_FIELD --- Makefile | 1 - src/backend/settings/__init__.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f37d9782..f07642d2 100644 --- a/Makefile +++ b/Makefile @@ -11,5 +11,4 @@ format: lint: flake8 && \ - pre-commit run && \ isort --check-only src diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index b80da22e..15f845e0 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -8,6 +8,7 @@ from corsheaders.defaults import default_headers +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" CORS_ALLOW_HEADERS = [*default_headers, "x-exporting", "exporting"] DOMAIN = os.getenv("DOMAIN") From 39a59972291363591d950f6b4d5ce455a63322bd Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 00:39:52 +0100 Subject: [PATCH 108/185] Update warnings filter in pyproject.toml --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 736b4708..f34d7bd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,10 @@ better-exceptions = "^0.3.3" [tool.pytest.ini_options] python_files = "tests.py test_*.py *_tests.py" +filterwarnings = """ +ignore::django.utils.deprecation.RemovedInDjango40Warning +ignore::django.utils.deprecation.RemovedInDjango41Warning +""" [tool.black] exclude = 'migrations' From d3ddfb415b6b2054c982208481b14aa3735d6163 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 00:40:33 +0100 Subject: [PATCH 109/185] Replace deprecated assertEquals with assertEqual --- src/admin/tests.py | 6 +++--- src/backend/tests.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/admin/tests.py b/src/admin/tests.py index 7b12c0c1..9fdccbc2 100644 --- a/src/admin/tests.py +++ b/src/admin/tests.py @@ -21,12 +21,12 @@ def test_missing_points(self): self.client.force_authenticate(user=self.user) response = self.client.get(reverse("self-check")) - self.assertEquals(response.data["d"][0]["issue"], "missing_points") + self.assertEqual(response.data["d"][0]["issue"], "missing_points") x.score = 5 x.save() response = self.client.get(reverse("self-check")) - self.assertEquals(len(response.data["d"]), 0) + self.assertEqual(len(response.data["d"]), 0) class BadFlagConfigTestCase(APITestCase): @@ -70,4 +70,4 @@ def test_length(self): self.client.force_authenticate(user=self.user) response = self.client.get(reverse("self-check")) - self.assertEquals(len(response.data["d"]), 14) + self.assertEqual(len(response.data["d"]), 14) diff --git a/src/backend/tests.py b/src/backend/tests.py index 9ab7cab9..04e959d6 100644 --- a/src/backend/tests.py +++ b/src/backend/tests.py @@ -5,4 +5,4 @@ class CatchAllTestCase(APITestCase): def test_catchall_404s(self): response = self.client.get("/sdgodgsjds") - self.assertEquals(response.status_code, HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) From 5564a5777a903a0abfa89f38d7b016b98022539f Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:03:41 +0100 Subject: [PATCH 110/185] Amend typo in Challenge mixin docstring --- src/challenge/tests/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index d4362984..cf8803dd 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -67,7 +67,7 @@ def setUp(self) -> None: self.user3.save() def get_json_for(self, challenge: "Challenge", data: dict[str, list[dict]]) -> Optional[dict]: - """Get the relevant serialized JSON for a specicifed challenge.""" + """Get the relevant serialized JSON for a specified challenge.""" for serialized_challenge in data.get("d", [{}])[0].get("challenges", ()): if serialized_challenge.get("id") == challenge.pk: return serialized_challenge From da7756e90550d435ba4d2c28e440143fafeae03e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:09:13 +0100 Subject: [PATCH 111/185] Run format pre-commit hook before lint --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9dd914b0..10cbadf3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,16 +11,16 @@ repos: - repo: local hooks: - - id: poetry-lint - name: Poetry Lint - description: This hook runs `make lint` inside a relevant poetry environment. - entry: poetry run make lint - language: system - types: [python] - - id: poetry-format name: Poetry Format description: This hook runs `make format` inside a relevant poetry environment. entry: poetry run make format language: system types: [python] + + - id: poetry-lint + name: Poetry Lint + description: This hook runs `make lint` inside a relevant poetry environment. + entry: poetry run make lint + language: system + types: [python] From 925e82c091d8eb8e2c7728ec81a59e4a793f9298 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:09:19 +0100 Subject: [PATCH 112/185] Bubble through all errors outside custom Exceptions --- src/backend/exception_handler.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/backend/exception_handler.py b/src/backend/exception_handler.py index c17dd0fb..f3550dc0 100644 --- a/src/backend/exception_handler.py +++ b/src/backend/exception_handler.py @@ -1,13 +1,11 @@ import traceback from typing import Optional -import sentry_sdk from django.conf import settings from django.http import Http404 from rest_framework import exceptions from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response -from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR from rest_framework.views import exception_handler from backend.exceptions import FormattedException @@ -47,9 +45,6 @@ def handle_exception(exc: Exception, context: dict) -> Optional[Response]: response.data = {"s": False, "m": exc.detail.code, "d": ""} else: - response = Response( - {"s": False, "m": "Internal server error.", "d": ""}, - status=HTTP_500_INTERNAL_SERVER_ERROR, - ) - sentry_sdk.capture_exception(exc) + # All other exceptions are raised as normal. + raise exc return response From a3c9ac0da410c626bd76a6923e83a2c47419f015 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:16:05 +0100 Subject: [PATCH 113/185] Add generic server error handler to backend/exception_handler.py --- src/backend/exception_handler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/exception_handler.py b/src/backend/exception_handler.py index f3550dc0..57567ccd 100644 --- a/src/backend/exception_handler.py +++ b/src/backend/exception_handler.py @@ -2,10 +2,11 @@ from typing import Optional from django.conf import settings -from django.http import Http404 +from django.http import Http404, HttpRequest from rest_framework import exceptions from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response +from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR from rest_framework.views import exception_handler from backend.exceptions import FormattedException @@ -48,3 +49,8 @@ def handle_exception(exc: Exception, context: dict) -> Optional[Response]: # All other exceptions are raised as normal. raise exc return response + + +def generic_error_response(request: HttpRequest, *args, **kwargs) -> Response: + """Return a generic error response for unexpected errors.""" + return Response({"s": False, "m": "Internal server error.", "d": ""}, status=HTTP_500_INTERNAL_SERVER_ERROR) From a62e14b9f6b25dba3a222ee78684fbda7b194be1 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:21:28 +0100 Subject: [PATCH 114/185] Add patch for serpy context --- src/challenge/serializers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index c3a6de2f..19cad0c8 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -127,6 +127,14 @@ class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): unlock_time_surpassed = serpy.MethodField() post_score_explanation = serpy.StrField() + def __init__(self, *args, **kwargs) -> None: + """Add the 'context' attribute to the serializer.""" + super().__init__(*args, **kwargs) + + if "context" in kwargs: + self.context = kwargs["context"] + setup_context(self.context) + class FastCategorySerializer(serpy.Serializer): id = serpy.IntField() From b6834c7c0b96ea0ba1df9c8e04da7bbd22b502b5 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:25:02 +0100 Subject: [PATCH 115/185] Default to the settings file with no dependencies --- src/backend/asgi.py | 2 +- src/backend/wsgi.py | 2 +- src/manage.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/asgi.py b/src/backend/asgi.py index 17515174..19e4c28d 100644 --- a/src/backend/asgi.py +++ b/src/backend/asgi.py @@ -11,7 +11,7 @@ import django from channels.routing import get_default_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings.lint") django.setup() diff --git a/src/backend/wsgi.py b/src/backend/wsgi.py index f96f227f..bf0853c2 100644 --- a/src/backend/wsgi.py +++ b/src/backend/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings.lint") application = get_wsgi_application() diff --git a/src/manage.py b/src/manage.py index a83a7e6e..1cdc4f1e 100755 --- a/src/manage.py +++ b/src/manage.py @@ -5,7 +5,7 @@ def main(): - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings.lint") try: from django.core.management import execute_from_command_line except ImportError as exc: From b739606c26012e44d0cd41bcf9eabddaa1e680ae Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 04:30:19 +0100 Subject: [PATCH 116/185] Don't add BrinIndex if we're not using PostgreSQL --- src/challenge/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/challenge/models.py b/src/challenge/models.py index dce32736..5d0f7710 100644 --- a/src/challenge/models.py +++ b/src/challenge/models.py @@ -1,5 +1,6 @@ import time +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.postgres.indexes import BrinIndex from django.db import models @@ -25,6 +26,8 @@ from config import config from plugins import plugins +USING_POSTGRES = settings.DATABASES.get("default", {}).get("ENGINE", "").endswith("postgresql") + class Category(ExportModelOperationsMixin("category"), models.Model): name = models.CharField(max_length=36, unique=True) @@ -229,7 +232,7 @@ class Meta: name="unique_member_challenge_correct", ), ] - indexes = [BrinIndex(fields=["challenge"], autosummarize=True)] + indexes = [BrinIndex(fields=["challenge"], autosummarize=True)] if USING_POSTGRES else [] def get_file_name(instance, filename): From db65f70a3bfb46f9c906e3cdacf5d09d4736a1ee Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 07:29:39 +0100 Subject: [PATCH 117/185] Update assertFalse statement in test_category_list_challenge_unlocked_admin --- src/challenge/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index cc94f1d7..3632c331 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -187,7 +187,7 @@ def test_category_list_challenge_unlocked_admin(self): self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse(self.get_json_for(self.challenge1, data=response.data)) + self.assertFalse(self.get_json_for(self.challenge1, data=response.data).get("unlocked")) def test_category_create(self): self.user.is_staff = True From 8bdd153cb8f8ab91d46e329876b9ab81f0c0da74 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 07:44:01 +0100 Subject: [PATCH 118/185] Refactor find_challenge_entry method --- src/challenge/tests/mixins.py | 13 ++++++++++--- src/challenge/tests/test_views.py | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index cf8803dd..bd2b3975 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Union from challenge.models import Category, Challenge from hint.models import Hint @@ -66,8 +66,15 @@ def setUp(self) -> None: self.user3.team = self.team2 self.user3.save() - def get_json_for(self, challenge: "Challenge", data: dict[str, list[dict]]) -> Optional[dict]: + def find_challenge_entry(self, challenge: "Challenge", data: Union[dict[str, list[dict]], list[dict]]) -> Optional[dict]: """Get the relevant serialized JSON for a specified challenge.""" - for serialized_challenge in data.get("d", [{}])[0].get("challenges", ()): + if type(data) is list: + challenges = data + elif type(data) is dict: + challenges = data.get("d", [{}])[0].get("challenges", ()) + else: + challenges = [] + + for serialized_challenge in challenges: if serialized_challenge.get("id") == challenge.pk: return serialized_challenge diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 3632c331..77d40592 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -173,21 +173,21 @@ def test_category_list_authenticated_content(self): def test_category_list_challenge_redacting(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse("description" in self.get_json_for(self.challenge1, data=response.data)) + self.assertFalse("description" in self.find_challenge_entry(self.challenge1, data=response.data)) def test_category_list_challenge_redacting_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse("description" in self.get_json_for(self.challenge3, data=response.data)) + self.assertFalse("description" in self.find_challenge_entry(self.challenge3, data=response.data)) def test_category_list_challenge_unlocked_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse(self.get_json_for(self.challenge1, data=response.data).get("unlocked")) + self.assertFalse(self.find_challenge_entry(self.challenge1, data=response.data).get("unlocked")) def test_category_create(self): self.user.is_staff = True @@ -242,7 +242,7 @@ def test_challenge_list_authenticated_content(self): def test_challenge_list_challenge_redacting(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("challenges-list")) - self.assertFalse("description" in response.data[-1]) + self.assertFalse("description" in self.find_challenge_entry(self.challenge3, data=response.data)) def test_challenge_list_challenge_redacting_admin(self): self.user.is_staff = True From fd3ee92b85f5ec4525e62a8f86b3fdb9956089e0 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 08:20:59 +0100 Subject: [PATCH 119/185] Update mixins.py --- src/challenge/tests/mixins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index bd2b3975..fe22cf80 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -68,12 +68,11 @@ def setUp(self) -> None: def find_challenge_entry(self, challenge: "Challenge", data: Union[dict[str, list[dict]], list[dict]]) -> Optional[dict]: """Get the relevant serialized JSON for a specified challenge.""" + challenges = [] if type(data) is list: challenges = data elif type(data) is dict: challenges = data.get("d", [{}])[0].get("challenges", ()) - else: - challenges = [] for serialized_challenge in challenges: if serialized_challenge.get("id") == challenge.pk: From d51b1f0a925e2d110f0e1b18daf14a9cc7945a58 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 08:33:58 +0100 Subject: [PATCH 120/185] Specify custom generic error view --- src/backend/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/urls.py b/src/backend/urls.py index ebf0f267..4b8a6e64 100644 --- a/src/backend/urls.py +++ b/src/backend/urls.py @@ -43,6 +43,7 @@ ] handler404 = CatchAllView.as_view() +handler500 = "backend.exception_handler.generic_error_response" if "silk" in settings.INSTALLED_APPS: urlpatterns += [path("silk/", include("silk.urls"))] From bc1559503091b37f3f68e430a2bfe5c2b4cd5d76 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Jun 2021 15:56:57 +0100 Subject: [PATCH 121/185] fix some tests --- src/challenge/serializers.py | 6 ++++++ src/challenge/tests/test_views.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 19cad0c8..e76ecb06 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -135,6 +135,12 @@ def __init__(self, *args, **kwargs) -> None: self.context = kwargs["context"] setup_context(self.context) + def serialize(self, instance): + if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ + not instance.hidden and instance.unlock_time_surpassed: + return super(FastChallengeSerializer, FastChallengeSerializer(instance, context=self.context)).to_value(instance) + return FastLockedChallengeSerializer(instance).data + class FastCategorySerializer(serpy.Serializer): id = serpy.IntField() diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 77d40592..27eb9636 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -171,8 +171,11 @@ def test_category_list_authenticated_content(self): self.assertEqual(len(response.data["d"][0]["challenges"]), 3) def test_category_list_challenge_redacting(self): + self.user.is_staff = False + self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) + print(self.find_challenge_entry(self.challenge1, data=response.data)) self.assertFalse("description" in self.find_challenge_entry(self.challenge1, data=response.data)) def test_category_list_challenge_redacting_admin(self): @@ -180,7 +183,7 @@ def test_category_list_challenge_redacting_admin(self): self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - self.assertFalse("description" in self.find_challenge_entry(self.challenge3, data=response.data)) + self.assertTrue("description" in self.find_challenge_entry(self.challenge3, data=response.data)) def test_category_list_challenge_unlocked_admin(self): self.user.is_staff = True @@ -257,7 +260,7 @@ def test_challenge_list_challenge_unlocked_admin(self): self.client.force_authenticate(self.user) response = self.client.get(reverse("challenges-list")) # TODO: Don't depend on order - self.assertFalse(response.data[-1]["unlocked"]) + self.assertFalse(self.find_challenge_entry(self.challenge1, data=response.data)["unlocked"]) def test_single_challenge_redacting(self): self.user.is_staff = False From f8f357f78b1caa5055e5e4241bc0d0f893978d9a Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 8 Jun 2021 17:12:27 +0100 Subject: [PATCH 122/185] Fix a failing test --- src/challenge/tests/mixins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index fe22cf80..e7b66df0 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -49,6 +49,7 @@ def setUp(self) -> None: flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000, + unlock_requirements="1" ) self.hint1 = Hint.objects.create(name="hint1", challenge=self.challenge1, text="a", penalty=100) self.hint2 = Hint.objects.create(name="hint2", challenge=self.challenge1, text="a", penalty=100) From befa1ccac872d157b64b96952ba6245c00eaea3a Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 8 Jun 2021 17:13:44 +0100 Subject: [PATCH 123/185] fix challenge tests --- src/challenge/serializers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index e76ecb06..1f2da24a 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -105,6 +105,9 @@ class FastLockedChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): release_time = DateTimeField() unlock_time_surpassed = serpy.MethodField() + def serialize(self, instance): + return self._serialize(instance, self._compiled_fields) + class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): id = serpy.IntField() @@ -135,11 +138,11 @@ def __init__(self, *args, **kwargs) -> None: self.context = kwargs["context"] setup_context(self.context) - def serialize(self, instance): + def _serialize(self, instance, fields): if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ not instance.hidden and instance.unlock_time_surpassed: - return super(FastChallengeSerializer, FastChallengeSerializer(instance, context=self.context)).to_value(instance) - return FastLockedChallengeSerializer(instance).data + return super(FastChallengeSerializer, self)._serialize(instance, fields) + return FastLockedChallengeSerializer(instance).serialize(instance) class FastCategorySerializer(serpy.Serializer): From 1f4e63ed15854633a92c421af65d375c9c33b787 Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 8 Jun 2021 17:54:33 +0100 Subject: [PATCH 124/185] send metadata --- src/challenge/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 1f2da24a..7e36f6c7 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -114,6 +114,7 @@ class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): name = serpy.StrField() description = serpy.StrField() challenge_type = serpy.StrField() + challenge_metadata = serpy.DictSerializer() flag_type = serpy.StrField() author = serpy.StrField() score = serpy.IntField() @@ -189,6 +190,7 @@ class FastAdminChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): name = serpy.StrField() description = serpy.StrField() challenge_type = serpy.StrField() + challenge_metadata = serpy.DictSerializer() flag_type = serpy.StrField() author = serpy.StrField() score = serpy.IntField() From b3b9e97cca8557723c1a7294495419b92c388803 Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 8 Jun 2021 17:57:16 +0100 Subject: [PATCH 125/185] shut beano up --- src/challenge/serializers.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 7e36f6c7..03c7cb5e 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -23,12 +23,13 @@ def setup_context(context): "votes_negative_counter": get_negative_votes(), } ) - if context["request"].user.team is not None: - context.update( - { - "solves": list(context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True)), - } - ) + if context["request"] and context["request"].user is not None: + if context["request"].user.team is not None: + context.update( + { + "solves": list(context["request"].user.team.solves.filter(correct=True).values_list("challenge", flat=True)), + } + ) class ForeignKeyField(serpy.Field): From 688bb7acbde332df7d01f3cd1940968afd62bd18 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 17:46:12 +0100 Subject: [PATCH 126/185] Add pytest testmon to project dependencies --- poetry.lock | 17 ++++++++++++++++- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index b3fcabf2..c581cb92 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1119,6 +1119,18 @@ pytest = ">=5.4.0" docs = ["sphinx", "sphinx-rtd-theme"] testing = ["django", "django-configurations (>=2.0)"] +[[package]] +name = "pytest-testmon" +version = "1.1.1" +description = "selects tests affected by changed files and methods" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = ">=4,<6" +pytest = ">=5,<7" + [[package]] name = "python-dateutil" version = "2.8.1" @@ -1527,7 +1539,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "6a74360f0112f580354789161fce49b5b578f21e6e9064d3156e19d0662c9b30" +content-hash = "9c0eabf10c79d0e59d96712eee6da4a2d2689b9ac94b17e144d3e0902fd48664" [metadata.files] aioredis = [ @@ -2199,6 +2211,9 @@ pytest-django = [ {file = "pytest-django-4.4.0.tar.gz", hash = "sha256:b5171e3798bf7e3fc5ea7072fe87324db67a4dd9f1192b037fed4cc3c1b7f455"}, {file = "pytest_django-4.4.0-py3-none-any.whl", hash = "sha256:65783e78382456528bd9d79a35843adde9e6a47347b20464eb2c885cb0f1f606"}, ] +pytest-testmon = [ + {file = "pytest-testmon-1.1.1.tar.gz", hash = "sha256:c8810f991545e352f646fb382e5962ff54b8aa52b09d62d35ae04f0d7a9c58d9"}, +] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, diff --git a/pyproject.toml b/pyproject.toml index f34d7bd9..28206653 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ isort = "^5.8.0" pre-commit = "^2.13.0" flake9 = "^3.8.3" better-exceptions = "^0.3.3" +pytest-testmon = "^1.1.1" [tool.pytest.ini_options] python_files = "tests.py test_*.py *_tests.py" From 29c15a8528cd3d2b8709a03cac00cdaf2fb143bf Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 17:46:35 +0100 Subject: [PATCH 127/185] Add testmon argument to pytest run --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f07642d2..d19d5ad4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ test: cd src && \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ - pytest --cov=. --cov-report=xml + pytest --cov=. --cov-report=xml --testmon format: isort -rc src && \ From 18e63d7b3c0a0d865ac79f953b300592cf907e10 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:07:25 +0100 Subject: [PATCH 128/185] Reconfigure coverage.py fail-under --- poetry.lock | 25 ++++++++++++++----------- pyproject.toml | 4 ++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index c581cb92..b23c752a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -173,20 +173,20 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "boto3" -version = "1.17.88" +version = "1.17.89" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] -botocore = ">=1.20.88,<1.21.0" +botocore = ">=1.20.89,<1.21.0" jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.4.0,<0.5.0" [[package]] name = "botocore" -version = "1.20.88" +version = "1.20.89" description = "Low-level, data-driven core of boto 3." category = "main" optional = false @@ -322,10 +322,13 @@ jinja2 = "*" name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["toml"] @@ -1318,7 +1321,7 @@ python-versions = ">=3.5" [[package]] name = "starkbank-ecdsa" -version = "1.1.0" +version = "1.1.1" description = "A lightweight and fast pure python ECDSA library" category = "main" optional = false @@ -1539,7 +1542,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "9c0eabf10c79d0e59d96712eee6da4a2d2689b9ac94b17e144d3e0902fd48664" +content-hash = "a7fbf186cf25b856f93a8d3d12ed856176d1387120d1b47806c7338a24521ca7" [metadata.files] aioredis = [ @@ -1598,12 +1601,12 @@ black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] boto3 = [ - {file = "boto3-1.17.88-py2.py3-none-any.whl", hash = "sha256:13afcc5e2fcc5e4f9eab1ee46a769cf738a259dcd45f71ee79255f18973e4584"}, - {file = "boto3-1.17.88.tar.gz", hash = "sha256:a715ca6c4457d56ea3e3efde9bdc8be41c29b2f2a904fbd12befdb9cb5e289e4"}, + {file = "boto3-1.17.89-py2.py3-none-any.whl", hash = "sha256:1f02cd513b130f9cd86c99836de6a0a5f78ea55110bdbc9011d9d78ff0fd3204"}, + {file = "boto3-1.17.89.tar.gz", hash = "sha256:06d8dca85a0bb66b7bf2721745895d44691c78dbe7eb3b146702aff85e34af34"}, ] botocore = [ - {file = "botocore-1.20.88-py2.py3-none-any.whl", hash = "sha256:be3cb73fab60a2349e2932bd0cbbe7e7736e3a2cd8c05b539d362ff3e406be76"}, - {file = "botocore-1.20.88.tar.gz", hash = "sha256:bc989edab52d4788aadd8d1aff925f5c6a7cbc68900bfdb8e379965aeac17317"}, + {file = "botocore-1.20.89-py2.py3-none-any.whl", hash = "sha256:e112f9a45db1c5a42f787e4b228a35da6e823bcba70f43f43005b4fb58066446"}, + {file = "botocore-1.20.89.tar.gz", hash = "sha256:ce0fa8bc260ad187824052805d224cee239d953bb4bfb1e52cf35ad79481b316"}, ] certifi = [ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, @@ -2340,7 +2343,7 @@ sqlparse = [ {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, ] starkbank-ecdsa = [ - {file = "starkbank-ecdsa-1.1.0.tar.gz", hash = "sha256:423f81bb55c896a3c85ee98ac7da98826721eaee918f5c0c1dfff99e1972da0c"}, + {file = "starkbank-ecdsa-1.1.1.tar.gz", hash = "sha256:f7b434b4a1e0ba082fb1804b908b79523973fd17b1fde377078857f7cee299d1"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/pyproject.toml b/pyproject.toml index 28206653..ceaeb843 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ serpy = "^0.3.1" django-zxcvbn-password-validator = "^1.3.2" uvicorn = {extras = ["standard"], version = "^0.13.4"} sentry-sdk = "^1.0.0" +coverage = {extras = ["toml"], version = "^5.5"} [tool.poetry.dev-dependencies] ipython = "^7.19.0" @@ -52,6 +53,9 @@ ignore::django.utils.deprecation.RemovedInDjango40Warning ignore::django.utils.deprecation.RemovedInDjango41Warning """ +[tool.coverage.run] +fail_under = 80 + [tool.black] exclude = 'migrations' line_length = 200 From 29450d6c29a38316456d4ed6dad26ab3691a4b1d Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:07:47 +0100 Subject: [PATCH 129/185] Add coverage arguments to Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d19d5ad4..2bc4c086 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ test: cd src && \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ - pytest --cov=. --cov-report=xml --testmon + pytest --testmon --cov=. --cov-report=xml --cov-fail-under=80 format: isort -rc src && \ From 09a6e5bbc2ffd4ff52017e5764a5de95dd7218b4 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:07:54 +0100 Subject: [PATCH 130/185] update .gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2bbd7c0..503d5947 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Created by https://www.toptal.com/developers/gitignore/api/python,jetbrains,linux,windows,code -# Edit at https://www.toptal.com/developers/gitignore?templates=python,jetbrains,linux,windows,code +# Persistent test caching +.testmondata ### Code ### .vscode/* From 37f4b782ec63864502b288d58c7c83d2fd27abfc Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:21:04 +0100 Subject: [PATCH 131/185] Use --testmon in Makefile test command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2bc4c086..c70435bc 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ test: cd src && \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ - pytest --testmon --cov=. --cov-report=xml --cov-fail-under=80 + pytest --testmon format: isort -rc src && \ From 22365ebe5f12820f8f73ef20266c7b4a829ef3a4 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:46:31 +0100 Subject: [PATCH 132/185] Add 'make test' to pre-commit hooks --- .pre-commit-config.yaml | 7 +++++++ Makefile | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10cbadf3..a6ba42cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,3 +24,10 @@ repos: entry: poetry run make lint language: system types: [python] + + - id: poetry-test + name: Poetry Test + description: This hook runs `make test` inside a relevant poetry environment. + entry: poetry run make test + language: system + types: [python] diff --git a/Makefile b/Makefile index c70435bc..4116cdcb 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,11 @@ test: cd src && \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ - pytest --testmon + pytest --testmon || \ + if [ $$? = 5 ] \ + then return 0; \ + else return $$?; \ + fi format: isort -rc src && \ From 1f054caa8ac90104ae161b25853e4c6612eb2c58 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 18:46:58 +0100 Subject: [PATCH 133/185] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4116cdcb..936b2712 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ test: BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ pytest --testmon || \ - if [ $$? = 5 ] \ + if [ $$? = 5 ]; \ then return 0; \ else return $$?; \ fi From 4439e5b6eee5f0b7a8d7654ce86543cd0db067e7 Mon Sep 17 00:00:00 2001 From: bentechy Date: Tue, 8 Jun 2021 20:06:15 +0100 Subject: [PATCH 134/185] fix first bloods --- src/challenge/serializers.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 03c7cb5e..45f1c28a 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -32,11 +32,11 @@ def setup_context(context): ) -class ForeignKeyField(serpy.Field): +class ForeignAttributeField(serpy.Field): """A :class:`Field` that gets a given attribute from a foreign object.""" def __init__(self, *args, attr_name="id", **kwargs): - super(ForeignKeyField, self).__init__(*args, **kwargs) + super(ForeignAttributeField, self).__init__(*args, **kwargs) self.attr_name = attr_name def to_value(self, value): @@ -64,7 +64,7 @@ class FastFileSerializer(serpy.Serializer): url = serpy.StrField() size = serpy.IntField() md5 = serpy.StrField() - challenge = ForeignKeyField() + challenge = ForeignAttributeField() class FastNestedTagSerializer(serpy.Serializer): @@ -115,7 +115,6 @@ class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): name = serpy.StrField() description = serpy.StrField() challenge_type = serpy.StrField() - challenge_metadata = serpy.DictSerializer() flag_type = serpy.StrField() author = serpy.StrField() score = serpy.IntField() @@ -124,7 +123,7 @@ class FastChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): files = FastFileSerializer(many=True) solved = serpy.MethodField() unlocked = serpy.MethodField() - first_blood = ForeignKeyField() + first_blood = ForeignAttributeField(attr_name="username") solve_count = serpy.MethodField() hidden = serpy.BoolField() votes = serpy.MethodField() @@ -200,7 +199,7 @@ class FastAdminChallengeSerializer(ChallengeSerializerMixin, serpy.Serializer): files = FastFileSerializer(many=True) solved = serpy.MethodField() unlocked = serpy.MethodField() - first_blood = ForeignKeyField() + first_blood = ForeignAttributeField(attr_name="username") solve_count = serpy.MethodField() hidden = serpy.BoolField() votes = serpy.MethodField() From 223217a844c8ab99612ef435794f056d1ef7dfe0 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 21:55:47 +0200 Subject: [PATCH 135/185] Add test case for leaving a team as non-owner. --- src/team/tests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/team/tests.py b/src/team/tests.py index 2705d0c4..c6231cc8 100644 --- a/src/team/tests.py +++ b/src/team/tests.py @@ -112,6 +112,26 @@ def test_team_leave_as_owner_without_members(self): config.set("enable_team_leave", False) self.assertEqual(response.status_code, HTTP_200_OK) + def test_team_leave_as_mortal(self) -> None: + """Leaving as non-owner should leave the team without deletion.""" + + # We create new regular user, and authenticate the request as a normal + # member of the team (a non-owner). + new_user = get_user_model()( + username="team-test-2", + email="team-test-2@example.org", + is_visible=True, + ) + new_user.team = self.team + new_user.save() + + self.client.force_authenticate(user=new_user) + + config.set("enable_team_leave", True) + response = self.client.post(reverse("team-leave")) + config.set("enable_team_leave", False) + self.assertEqual(response.status_code, HTTP_200_OK) + class CreateTeamTestCase(TeamSetupMixin, APITestCase): def test_create_team(self): From a61a5dab57659ecb5d4dde8337b4ff964e2fc7fa Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 22:07:22 +0200 Subject: [PATCH 136/185] Exclude migrations from coverage calculation. These are mostly generated, and we do not test rollbacks in the first place. --- src/.coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/.coveragerc b/src/.coveragerc index be69d5fa..f33196fb 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -11,6 +11,8 @@ omit = backend/settings/* ractf/management/* gunicorn_conf.py + */migrations/*.py + [report] fail_under = 100 show_missing = True From 90ccd79ba4121f93b5ca352526e322982b42e6b2 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 22:08:30 +0200 Subject: [PATCH 137/185] Fix typo for gunicorn configuration path. This is not tested in CI either, as it is only relevant to the production deployment. --- src/.coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/.coveragerc b/src/.coveragerc index f33196fb..cd6d7824 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -10,7 +10,7 @@ omit = sockets/routing.py backend/settings/* ractf/management/* - gunicorn_conf.py + gunicorn_config.py */migrations/*.py [report] From 47d1a1c45513b5d63934df6026d70088c46ceb07 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Jun 2021 21:13:52 +0100 Subject: [PATCH 138/185] https://www.youtube.com/watch?v=LDU_Txk06tM --- src/challenge/views.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/challenge/views.py b/src/challenge/views.py index 520939b6..a8dde22e 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -122,18 +122,6 @@ def list(self, request, *args, **kwargs): challenge["votes"] = {"positive": positive_votes.get(challenge["id"], 0), "negative": negative_votes.get(challenge["id"], 0)} challenge["solve_count"] = solve_counts.get(challenge["id"], 0) - # This is to fix an issue with django duplicating challenges on .annotate. - # If you want to clean this up, good luck. - for category in categories: - unlocked = set() - for challenge in category["challenges"]: - if "unlocked" in challenge and challenge["unlocked"]: - unlocked.add(challenge["id"]) - new_challenges = [] - for challenge in category["challenges"]: - if not (("unlocked" not in challenge or not challenge["unlocked"]) and challenge["id"] in unlocked): - new_challenges.append(challenge) - category["challenges"] = new_challenges return FormattedResponse(categories) From 791f0c5d9a7becf74c42231b211576499f08b4dd Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 21:31:08 +0100 Subject: [PATCH 139/185] Add 'coverage' step to Makefile --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 936b2712..fb149119 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,13 @@ test: else return $$?; \ fi +coverage: + export DJANGO_SETTINGS_MODULE='backend.settings.lint' && \ + cd src && \ + BETTER_EXCEPTIONS=1 \ + python manage.py migrate && \ + pytest --cov=. --cov-report=xml + format: isort -rc src && \ black src From 11593f8d6e027f2d61cf079687f829a9733f3e25 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 21:34:02 +0100 Subject: [PATCH 140/185] Lower fail_under percentage for coverage --- src/.coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/.coveragerc b/src/.coveragerc index cd6d7824..f3494f79 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -14,7 +14,7 @@ omit = */migrations/*.py [report] -fail_under = 100 +fail_under = 80 show_missing = True skip_covered = True exclude_lines = From 19a9a9f904c556a431031d41569717a3dc3d0260 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Jun 2021 21:38:53 +0100 Subject: [PATCH 141/185] add html coverage to the makefile --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index fb149119..adee4708 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,13 @@ coverage: python manage.py migrate && \ pytest --cov=. --cov-report=xml +coveragehtml: + export DJANGO_SETTINGS_MODULE='backend.settings.lint' && \ + cd src && \ + BETTER_EXCEPTIONS=1 \ + python manage.py migrate && \ + pytest --cov=. --cov-report=html + format: isort -rc src && \ black src From 9d93b9a724c3a320d28fb4c5d9ddf119325c2d37 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 21:39:11 +0100 Subject: [PATCH 142/185] Update Makefile --- Makefile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index adee4708..ca419dea 100644 --- a/Makefile +++ b/Makefile @@ -14,14 +14,9 @@ coverage: cd src && \ BETTER_EXCEPTIONS=1 \ python manage.py migrate && \ - pytest --cov=. --cov-report=xml - -coveragehtml: - export DJANGO_SETTINGS_MODULE='backend.settings.lint' && \ - cd src && \ - BETTER_EXCEPTIONS=1 \ - python manage.py migrate && \ - pytest --cov=. --cov-report=html + pytest --cov=. --cov-report=xml && \ + coverage html && \ + xdg-open htmlcov/index.html format: isort -rc src && \ From 2a9fb29132f5f55b2997261c400655d26aad9577 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 22:41:38 +0200 Subject: [PATCH 143/185] Kill unused module. --- src/plugins/exceptions.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 src/plugins/exceptions.py diff --git a/src/plugins/exceptions.py b/src/plugins/exceptions.py deleted file mode 100644 index 1b4214cd..00000000 --- a/src/plugins/exceptions.py +++ /dev/null @@ -1,2 +0,0 @@ -class InvalidPluginException(Exception): - pass From 92fe59e5cc044cb01de04a7955f83c4c2bf9dbf3 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 22:00:38 +0100 Subject: [PATCH 144/185] Add nplusone and django-querycount to dev dependencies --- poetry.lock | 40 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 ++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index b23c752a..fe205227 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,6 +171,14 @@ typing-extensions = ">=3.7.4" colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +[[package]] +name = "blinker" +version = "1.4" +description = "Fast, simple object-to-object and broadcast signaling" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "boto3" version = "1.17.89" @@ -444,6 +452,14 @@ python-versions = "*" [package.dependencies] prometheus-client = ">=0.7" +[[package]] +name = "django-querycount" +version = "0.7.0" +description = "Middleware that Prints the number of DB queries to the runserver console." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "django-redis" version = "4.11.0" @@ -864,6 +880,18 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "nplusone" +version = "1.0.0" +description = "Detecting the n+1 queries problem in Python" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +blinker = ">=1.3" +six = ">=1.9.0" + [[package]] name = "packaging" version = "20.9" @@ -1542,7 +1570,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "a7fbf186cf25b856f93a8d3d12ed856176d1387120d1b47806c7338a24521ca7" +content-hash = "69090c35de99999f14fef37ca924201f405609cc48f60e374abe5e60ba8f2ecc" [metadata.files] aioredis = [ @@ -1600,6 +1628,9 @@ better-exceptions = [ black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] +blinker = [ + {file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"}, +] boto3 = [ {file = "boto3-1.17.89-py2.py3-none-any.whl", hash = "sha256:1f02cd513b130f9cd86c99836de6a0a5f78ea55110bdbc9011d9d78ff0fd3204"}, {file = "boto3-1.17.89.tar.gz", hash = "sha256:06d8dca85a0bb66b7bf2721745895d44691c78dbe7eb3b146702aff85e34af34"}, @@ -1787,6 +1818,9 @@ django-prometheus = [ {file = "django-prometheus-2.1.0.tar.gz", hash = "sha256:dd3f8da1399140fbef5c00d1526a23d1ade286b144281c325f8e409a781643f2"}, {file = "django_prometheus-2.1.0-py2.py3-none-any.whl", hash = "sha256:c338d6efde1ca336e90c540b5e87afe9287d7bcc82d651a778f302b0be17a933"}, ] +django-querycount = [ + {file = "django-querycount-0.7.0.tar.gz", hash = "sha256:8f5123d78716ff0704f2373e746a7200b8d8417798ce4a99bf2de87e3768f9ce"}, +] django-redis = [ {file = "django-redis-4.11.0.tar.gz", hash = "sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b"}, {file = "django_redis-4.11.0-py3-none-any.whl", hash = "sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4"}, @@ -2063,6 +2097,10 @@ nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] +nplusone = [ + {file = "nplusone-1.0.0-py2.py3-none-any.whl", hash = "sha256:96b1e6e29e6af3e71b67d0cc012a5ec8c97c6a2f5399f4ba41a2bbe0e253a9ac"}, + {file = "nplusone-1.0.0.tar.gz", hash = "sha256:1726c0a10c0aa7eabb04e24db2882ff97b6b7ee29d729a8d97dcbd12ef5a5651"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, diff --git a/pyproject.toml b/pyproject.toml index ceaeb843..7c10d8a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,8 @@ pre-commit = "^2.13.0" flake9 = "^3.8.3" better-exceptions = "^0.3.3" pytest-testmon = "^1.1.1" +django-querycount = "^0.7.0" +nplusone = "^1.0.0" [tool.pytest.ini_options] python_files = "tests.py test_*.py *_tests.py" From 28781d0e26bde65fe3a3bdd951672516055ebc47 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 22:07:55 +0100 Subject: [PATCH 145/185] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a14c9a98..691cb769 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.9-slim ARG BUILD_DEPS="build-essential curl" RUN set -ex \ - && apt-get update && apt-get -y --no-install-recommends install $BUILD_DEPS libpq-dev netcat git \ + && apt-get update && apt-get -y --no-install-recommends install $BUILD_DEPS libpq-dev netcat make git \ && rm -rf /var/lib/apt/lists/* \ && curl -sSL "https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py" | python \ && . $HOME/.poetry/env \ From 80d3a659f3a7c3db41da0b2eeb1f0bed1c4a1785 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 22:53:18 +0200 Subject: [PATCH 146/185] Raise ValidationError for already verified email. --- src/authentication/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 37709157..f72c7dcd 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -140,7 +140,7 @@ def validate(self, data): token = data.get("token") user = get_object_or_404(get_user_model(), id=uid, email_token=token) if user.email_verified: - raise FormattedException(m="email_already_verified", status=HTTP_403_FORBIDDEN) + raise serializers.ValidationError("email is already verified") data["user"] = user return data From 74450c9c64abfdab81803a599c0b727642c3cb2e Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 22:55:10 +0200 Subject: [PATCH 147/185] Remove double verification. The serializer already checked this. --- src/authentication/serializers.py | 2 +- src/authentication/tests.py | 4 ++-- src/authentication/views.py | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index f72c7dcd..07fa44ca 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -151,7 +151,7 @@ class EmailSerializer(serializers.Serializer): def validate(self, data): user = get_object_or_404(get_user_model(), email=data.get("email")) if user.email_verified: - raise FormattedException(m="email_already_verified", status=HTTP_403_FORBIDDEN) + raise serializers.ValidationError("email is already verified") data["user"] = user return data diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 9742fad0..3f2a4061 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -183,7 +183,7 @@ def test_already_verified_email_resend(self): user = get_user_model()(username="resend-email", email_verified=True, email="tvu@example.com") user.save() response = self.client.post(reverse("resend-email"), {"email": "tvu@example.com"}) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_non_existing_email_resend(self): with self.settings(RATELIMIT_ENABLE=False): @@ -719,7 +719,7 @@ def test_email_verify_twice(self): } response = self.client.post(reverse("verify-email"), data) response = self.client.post(reverse("verify-email"), data) - self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_email_verify_bad_token(self): data = { diff --git a/src/authentication/views.py b/src/authentication/views.py index f633f5bc..ff0a3ac5 100644 --- a/src/authentication/views.py +++ b/src/authentication/views.py @@ -279,14 +279,15 @@ def post(self, request): d=serializer.errors, status=HTTP_400_BAD_REQUEST, ) + + # Already verified email is checked in the email serializer. user = serializer.validated_data["user"] - if user.email_verified: - return FormattedResponse( - m="invalid_token_or_uid", - d=serializer.errors, - status=HTTP_400_BAD_REQUEST, - ) - send_email(user.email, "RACTF - Verify your email", "verify", url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token)) + send_email( + user.email, + "RACTF - Verify your email", + "verify", + url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token), + ) return FormattedResponse("email_resent") From f55adf1380a03b63c08391a0a834b47b631a9e15 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 22:19:10 +0100 Subject: [PATCH 148/185] Update WORKDIR in Dockerfile --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 691cb769..a8c6c508 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,4 +23,3 @@ RUN poetry install --no-root --no-interaction \ COPY . /app/ EXPOSE 8000 -WORKDIR /app/src/ From 333fa97bae4d9cdbc37ec123596c81aa0aae7618 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 23:23:12 +0200 Subject: [PATCH 149/185] Do not cover branch. Additionally, document why we delete a user on failure, per Dave's comment. --- src/authentication/serializers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/authentication/serializers.py b/src/authentication/serializers.py index 07fa44ca..cdb9677c 100644 --- a/src/authentication/serializers.py +++ b/src/authentication/serializers.py @@ -79,7 +79,16 @@ def create(self, validated_data): user.save() try: send_email(user.email, "RACTF - Verify your email", "verify", url=settings.FRONTEND_URL + "verify?id={}&secret={}".format(user.id, user.email_token)) - except SMTPException: + except SMTPException: # pragma: no cover - prod error handling + # Whilst the API can resend verification emails, + # the frontend doesnt have that implemented, in + # addition to that, if smtp fails that early they are + # going to have to do something regardless, so it is + # easier [for us] to fail out of creating the user and + # leave them on the register page, telling them something + # went wrong then being confused why their email isnt there. + # [Via Dave on Discord] + user.delete() raise FormattedException(m="creation_failed") From efef22218341a70a1df250710042c10ac3ac0a57 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 22:21:31 +0100 Subject: [PATCH 150/185] Update docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2121ac6a..ee01b31a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,7 @@ services: backend: &backend build: . entrypoint: /app/entrypoints/backend.sh - command: ./manage.py runserver 0.0.0.0:8000 + command: python src/manage.py runserver 0.0.0.0:8000 volumes: - .:/app environment: From 806b0975bf2658dfb0aef26fcdd01ea2cde841b3 Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Tue, 8 Jun 2021 22:30:09 +0100 Subject: [PATCH 151/185] Remove coverage configuration from pyproject.toml --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7c10d8a1..c94d478b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,9 +55,6 @@ ignore::django.utils.deprecation.RemovedInDjango40Warning ignore::django.utils.deprecation.RemovedInDjango41Warning """ -[tool.coverage.run] -fail_under = 80 - [tool.black] exclude = 'migrations' line_length = 200 From a6e3ff259dbb4089a715a7ea4ce0bc491d01a0fe Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 23:48:35 +0200 Subject: [PATCH 152/185] Add test for uncached categories. --- src/challenge/tests/test_views.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 27eb9636..e6acc954 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -220,6 +220,22 @@ def test_category_create_unauthorized(self): ) self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) + # This needs to be the last test. + # Why? Because otherwise, it breaks one other test. Please, do not + # ask me about what's going on here. Is it cache validation? Is it + # an app we're using? Is this the real life? Is this just fantasy? + def test_category_list_authenticated_content_without_cache(self) -> None: + """List categories with caching disabled.""" + + try: + config.set("enable_caching", False) + self.client.force_authenticate(self.user) + response = self.client.get(reverse("categories-list")) + self.assertEqual(len(response.data["d"]), 1) + self.assertEqual(len(response.data["d"][0]["challenges"]), 3) + finally: + config.set("enable_caching", True) + class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): def test_challenge_list_unauthenticated_permission(self): From d35b878e32b90433e293c49a5f75bddab1d80c93 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 8 Jun 2021 23:55:55 +0200 Subject: [PATCH 153/185] Revert "Add test for uncached categories." This reverts commit a6e3ff259dbb4089a715a7ea4ce0bc491d01a0fe. --- src/challenge/tests/test_views.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index e6acc954..27eb9636 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -220,22 +220,6 @@ def test_category_create_unauthorized(self): ) self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) - # This needs to be the last test. - # Why? Because otherwise, it breaks one other test. Please, do not - # ask me about what's going on here. Is it cache validation? Is it - # an app we're using? Is this the real life? Is this just fantasy? - def test_category_list_authenticated_content_without_cache(self) -> None: - """List categories with caching disabled.""" - - try: - config.set("enable_caching", False) - self.client.force_authenticate(self.user) - response = self.client.get(reverse("categories-list")) - self.assertEqual(len(response.data["d"]), 1) - self.assertEqual(len(response.data["d"][0]["challenges"]), 3) - finally: - config.set("enable_caching", True) - class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): def test_challenge_list_unauthenticated_permission(self): From 75c0dab4cc4ff43478dae23a4ba26bf4026c9988 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Jun 2021 23:14:07 +0100 Subject: [PATCH 154/185] test token and fix config persisting between tests --- Makefile | 4 ++-- src/authentication/tests.py | 7 +++++++ src/challenge/serializers.py | 3 +-- src/challenge/tests/mixins.py | 2 +- src/config/backends.py | 3 ++- src/config/views.py | 2 +- src/plugins/tests.py | 1 + 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index ca419dea..0b133a6c 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ test: python manage.py migrate && \ pytest --testmon || \ if [ $$? = 5 ]; \ - then return 0; \ - else return $$?; \ + then exit 0; \ + else exit $$?; \ fi coverage: diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 3f2a4061..53da2cbe 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -501,6 +501,13 @@ def test_token_str(self): tok = Token(key="a" * 40, user=user) self.assertEqual(str(tok), "a" * 40) + def test_token_preserves_key(self): + user = get_user_model()(username="token-test-2", email="token-test-2@example.org") + user.save() + token = Token(key="a" * 40, user=user) + token.save() + self.assertEqual(token.key, "a" * 40) + class TFATestCase(APITestCase): def setUp(self): diff --git a/src/challenge/serializers.py b/src/challenge/serializers.py index 45f1c28a..70048bab 100644 --- a/src/challenge/serializers.py +++ b/src/challenge/serializers.py @@ -140,8 +140,7 @@ def __init__(self, *args, **kwargs) -> None: setup_context(self.context) def _serialize(self, instance, fields): - if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and \ - not instance.hidden and instance.unlock_time_surpassed: + if instance.is_unlocked(self.context["request"].user, solves=self.context.get("solves", None)) and not instance.hidden and instance.unlock_time_surpassed: return super(FastChallengeSerializer, self)._serialize(instance, fields) return FastLockedChallengeSerializer(instance).serialize(instance) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index e7b66df0..8cbe208f 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -49,7 +49,7 @@ def setUp(self) -> None: flag_metadata={"flag": "ractf{a}"}, author="dave", score=1000, - unlock_requirements="1" + unlock_requirements="1", ) self.hint1 = Hint.objects.create(name="hint1", challenge=self.challenge1, text="a", penalty=100) self.hint2 = Hint.objects.create(name="hint2", challenge=self.challenge1, text="a", penalty=100) diff --git a/src/config/backends.py b/src/config/backends.py index b39a0900..e7624691 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -1,4 +1,5 @@ import abc +import sys from django.core.cache import caches from django.db.models.query import QuerySet @@ -87,7 +88,7 @@ def load(self, defaults): if config_exists: config = db_config[0].value - if "config_version" not in config or config["config_version"] < defaults["config_version"]: + if "config_version" not in config or config["config_version"] < defaults["config_version"] or "test" in sys.argv: for key, value in defaults.items(): self.set(key, value) return diff --git a/src/config/views.py b/src/config/views.py index b40e4a15..99f1cf7b 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -20,7 +20,7 @@ def get(self, request, name=None): if request.user.is_superuser: return FormattedResponse(config.get_all()) return FormattedResponse(config.get_all_non_sensitive()) - if not config.config.is_sensitive(name) or request.is_superuser: + if not config.is_sensitive(name) or request.is_superuser: return FormattedResponse(config.get(name)) return FormattedResponse(status=HTTP_403_FORBIDDEN) diff --git a/src/plugins/tests.py b/src/plugins/tests.py index 84d054fa..c94af96f 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -207,6 +207,7 @@ def test_recalculate(self): self.assertTrue(get_user_model().objects.get(id=self.user.id).points < points) def test_score(self): + config.set("enable_scoring", True) self.plugin.score(self.user, self.team, "", Solve.objects.filter(challenge=self.challenge2)) self.assertEqual(self.team.points, 1000) self.assertEqual(self.team.leaderboard_points, 1000) From e99af519ab51fe8335035daad455bda258f6fa4f Mon Sep 17 00:00:00 2001 From: David Date: Tue, 8 Jun 2021 23:34:37 +0100 Subject: [PATCH 155/185] make targets --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index 0b133a6c..7ad9b7e2 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,11 @@ format: lint: flake8 && \ isort --check-only src + +dev-server: + docker-compose build && \ + docker-compose up -d + +dev-test: + make dev-server && \ + docker-compose exec backend pytest --cov=src src From 54780bcd05f089fd60dc5b448eaa1b55896701dc Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 00:00:14 +0100 Subject: [PATCH 156/185] remove polaris app, its not compatible with the current api and we should consider a more generic api to support andromeda and polaris --- src/backend/settings/__init__.py | 1 - src/backend/urls.py | 1 - src/polaris/__init__.py | 0 src/polaris/admin.py | 1 - src/polaris/apps.py | 5 -- src/polaris/client.py | 102 ----------------------------- src/polaris/migrations/__init__.py | 0 src/polaris/models.py | 1 - src/polaris/tests.py | 1 - src/polaris/urls.py | 16 ----- src/polaris/views.py | 84 ------------------------ 11 files changed, 212 deletions(-) delete mode 100644 src/polaris/__init__.py delete mode 100644 src/polaris/admin.py delete mode 100644 src/polaris/apps.py delete mode 100644 src/polaris/client.py delete mode 100644 src/polaris/migrations/__init__.py delete mode 100644 src/polaris/models.py delete mode 100644 src/polaris/tests.py delete mode 100644 src/polaris/urls.py delete mode 100644 src/polaris/views.py diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index 15f845e0..aad91922 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -99,7 +99,6 @@ "member.apps.MemberConfig", "pages.apps.PagesConfig", "plugins.apps.PluginsConfig", - "polaris.apps.PolarisConfig", "ractf.apps.RactfConfig", "scorerecalculator.apps.ScorerecalculatorConfig", "team.apps.TeamConfig", diff --git a/src/backend/urls.py b/src/backend/urls.py index 4b8a6e64..da4ee49b 100644 --- a/src/backend/urls.py +++ b/src/backend/urls.py @@ -34,7 +34,6 @@ path("team/", include("team.urls")), path("pages/", include("pages.urls")), path("experiments/", include("experiments.urls")), - path("polaris/", include("polaris.urls")), ] urlpatterns = [ diff --git a/src/polaris/__init__.py b/src/polaris/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/polaris/admin.py b/src/polaris/admin.py deleted file mode 100644 index 846f6b40..00000000 --- a/src/polaris/admin.py +++ /dev/null @@ -1 +0,0 @@ -# Register your models here. diff --git a/src/polaris/apps.py b/src/polaris/apps.py deleted file mode 100644 index fcb173f2..00000000 --- a/src/polaris/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class PolarisConfig(AppConfig): - name = "polaris" diff --git a/src/polaris/client.py b/src/polaris/client.py deleted file mode 100644 index 7d85a3d2..00000000 --- a/src/polaris/client.py +++ /dev/null @@ -1,102 +0,0 @@ -import requests -from django.conf import settings -from requests.auth import HTTPBasicAuth -from rest_framework.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN - -from backend.exceptions import FormattedException - - -def handle_request(method, path, **kwargs): - response = requests.request(method, f"{settings.POLARIS_URL}/{path}", auth=HTTPBasicAuth(settings.POLARIS_USERNAME, settings.POLARIS_PASSWORD), **kwargs) - if 200 <= response.status_code < 300: - return response.json() - elif response.status_code == HTTP_401_UNAUTHORIZED: - raise FormattedException(status_code=HTTP_401_UNAUTHORIZED, m="polaris_unauthorized") - elif response.status_code == HTTP_403_FORBIDDEN: - raise FormattedException(status_code=HTTP_403_FORBIDDEN, m="polaris_permission_denied") - else: - raise FormattedException(m="polaris_error", d=response.json()) - - -def get(path, **kwargs): - return handle_request("GET", path, **kwargs) - - -def post(path, **kwargs): - return handle_request("POST", path, **kwargs) - - -def put(path, **kwargs): - return handle_request("PUT", path, **kwargs) - - -def delete(path, **kwargs): - return handle_request("DELETE", path, **kwargs) - - -def list_challenges(filter=""): - return get(f"/challenges?filter={filter}") - - -def get_challenge(challenge): - return get(f"/challenges/{challenge}") - - -def submit_challenge(challenge): - return post("/challenges", json=challenge) - - -def delete_challenge(challenge): - return post(f"/challenges/{challenge}") - - -def list_deployments(deployment_filter="", challenge_filter=""): - return get(f"/deployments?filter={deployment_filter}&challengefilter={challenge_filter}") - - -def get_deployment(deployment): - return get(f"/deployments/{deployment}") - - -def submit_deployment(deployment): - return post("/deployments", json=deployment) - - -def delete_deployment(deployment): - return post(f"/deployments/{deployment}") - - -def list_hosts(filter=""): - return get(f"/hosts?filter={filter}") - - -def get_host(id): - return get(f"/hosts/{id}") - - -def censor_instance(instance): - del instance["randomEnv"] - ports = [] - for port in instance["ports"]: - if port["advertised"]: - ports.append(port) - instance["ports"] = ports - return instance - - -def allocate_instance(challenge_id, user): - response = post("/instanceallocation", json={"challenge": str(challenge_id), "user": str(user.id), "team": str(user.team.id)}) - if not user.is_superuser: - return censor_instance(response) - return response - - -def reallocate_instance(challenge_id, user): - response = post("/instanceallocation/new", json={"challenge": str(challenge_id), "user": str(user.id), "team": str(user.team.id)}) - if not user.is_superuser: - return censor_instance(response) - return response - - -def list_instances(host_filter="", challenge_filter=""): - return get(f"/instances?hostfilter={host_filter}&challengefilter={challenge_filter}") diff --git a/src/polaris/migrations/__init__.py b/src/polaris/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/polaris/models.py b/src/polaris/models.py deleted file mode 100644 index 6b202199..00000000 --- a/src/polaris/models.py +++ /dev/null @@ -1 +0,0 @@ -# Create your models here. diff --git a/src/polaris/tests.py b/src/polaris/tests.py deleted file mode 100644 index a39b155a..00000000 --- a/src/polaris/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/src/polaris/urls.py b/src/polaris/urls.py deleted file mode 100644 index eafea02d..00000000 --- a/src/polaris/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.urls import include, path -from rest_framework.routers import DefaultRouter - -from polaris import views - -router = DefaultRouter() -router.register("challenge/", views.ChallengeViewset, basename="polaris-challenge") -router.register("deployment/", views.DeploymentViewset, basename="polaris-deployment") -router.register("host/", views.HostViewset, basename="polaris-host") - -urlpatterns = [ - path("get_instance/", views.GetInstanceView.as_view(), name="polaris-get-instance"), - path("new_instance/", views.ResetInstanceView.as_view(), name="polaris-reset-instance"), - path("", include(router.urls), name="polaris"), - path("instances/", views.ListInstancesView.as_view(), name="polaris-list-instances"), -] diff --git a/src/polaris/views.py b/src/polaris/views.py deleted file mode 100644 index 81cb8dc4..00000000 --- a/src/polaris/views.py +++ /dev/null @@ -1,84 +0,0 @@ -from rest_framework import viewsets -from rest_framework.generics import get_object_or_404 -from rest_framework.permissions import IsAdminUser, IsAuthenticated -from rest_framework.views import APIView - -from backend.response import FormattedResponse -from challenge.models import Challenge -from polaris import client - - -class GetInstanceView(APIView): - permission_classes = (IsAuthenticated,) - throttle_scope = "challenge_instance_get" - - def get(self, request, challenge_id): - return FormattedResponse(client.allocate_instance(challenge_id, request.user)) - - -class ResetInstanceView(APIView): - permission_classes = (IsAuthenticated,) - throttle_scope = "challenge_instance_reset" - - def get(self, request, challenge_id): - return FormattedResponse(client.reallocate_instance(challenge_id, request.user)) - - -class ChallengeViewset(viewsets.ViewSet): - permission_classes = (IsAdminUser,) - throttle_scope = "polaris_challenges" - - def list(self, request): - return FormattedResponse(client.list_challenges(request.GET.get("filter", ""))) - - def retrieve(self, request, pk): - return FormattedResponse(client.get_challenge(pk)) - - def destroy(self, request, pk): - return FormattedResponse(client.delete_challenge(pk)) - - def create(self, request): - challenge_id = request.GET.get("challenge_id", "") - if challenge_id == "": - return FormattedResponse(client.submit_challenge(request.data)) - challenge = get_object_or_404(Challenge.objects, challenge_id) - response = client.submit_challenge(challenge) - challenge.challenge_metadata["cserv_name"] = response.data["id"] - challenge.save() - return response - - -class DeploymentViewset(viewsets.ViewSet): - permission_classes = (IsAdminUser,) - throttle_scope = "polaris_deployments" - - def list(self, request): - return FormattedResponse(client.list_deployments(request.GET.get("deploymentfilter", ""), request.GET.get("challengefilter", ""))) - - def retrieve(self, request, pk): - return FormattedResponse(client.get_deployment(pk)) - - def create(self, request): - return FormattedResponse(client.submit_deployment(request.data)) - - def destroy(self, request, pk): - return FormattedResponse(client.delete_deployment(pk)) - - -class HostViewset(viewsets.ViewSet): - permission_classes = (IsAdminUser,) - throttle_scope = "polaris_hosts" - - def list(self, request): - return FormattedResponse(client.list_hosts(request.GET.get("filter", ""))) - - def retrieve(self, request, pk): - return FormattedResponse(client.get_host(pk)) - - -class ListInstancesView(APIView): - permission_classes = (IsAdminUser,) - throttle_scope = "polaris_view_instances" - - def get(self, request): - return FormattedResponse(client.list_instances(request.GET.get("hostfilter", ""), request.GET.get("challengefilter", ""))) From 2d6d977c7df82172b0ce98b905a54a2aa29b37f7 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 00:30:26 +0100 Subject: [PATCH 157/185] 100% coverage on auth views --- src/authentication/tests.py | 40 +++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index 53da2cbe..e9286188 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -2,7 +2,9 @@ import pyotp from django.contrib.auth import get_user_model +from django.http import HttpRequest from django.urls import reverse +from rest_framework.request import Request from rest_framework.status import ( HTTP_200_OK, HTTP_201_CREATED, @@ -24,6 +26,7 @@ AddTwoFactorView, ChangePasswordView, CreateBotView, + DesudoView, DoPasswordResetView, LoginTwoFactorView, LoginView, @@ -203,6 +206,18 @@ def test_sudo(self): self.assertEqual(req.status_code, HTTP_200_OK) +class DesudoTestCase(APITestCase): + def test_desudo(self): + user2 = get_user_model()(username="sudotest2", email="sudotest2@example.com") + user2.save() + + request = Request(HttpRequest()) + request.sudo_from = user2 + + response = DesudoView().post(request) + self.assertTrue("token" in response.data["d"]) + + class GenerateInvitesTestCase(APITestCase): def test_response_length(self): user = get_user_model()(username="resend-email", is_staff=True, email="tvu@example.com", is_superuser=True) @@ -336,6 +351,7 @@ def setUp(self): LoginView.throttle_scope = "" def test_login(self): + self.user.set_password("password") data = { "username": "login-test", "password": "password", @@ -392,6 +408,7 @@ def test_login_inactive(self): self.user.save() def test_login_with_email(self): + self.user.set_password("password") data = { "username": "login-test@example.org", "password": "password", @@ -483,7 +500,7 @@ def test_login_2fa_missing(self): response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) - def test_login_2fa_backup_cde(self): + def test_login_2fa_backup_code(self): BackupCode(user=self.user, code="12345678").save() data = { "username": "login-test", @@ -493,6 +510,25 @@ def test_login_2fa_backup_cde(self): response = self.client.post(reverse("login-2fa"), data) self.assertEqual(response.status_code, HTTP_200_OK) + def test_login_2fa_backup_code_invalid(self): + BackupCode(user=self.user, code="12345678").save() + data = { + "username": "login-test", + "password": "password", + "tfa": "87654321", + } + response = self.client.post(reverse("login-2fa"), data) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) + + def test_login_2fa_invalid_code(self): + data = { + "username": "login-test", + "password": "password", + "tfa": "123456789", + } + response = self.client.post(reverse("login-2fa"), data) + self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) + class TokenTestCase(APITestCase): def test_token_str(self): @@ -640,7 +676,7 @@ def test_password_reset_issues_token(self): "password": "uO7*$E@0ngqL", } response = self.client.post(reverse("do-password-reset"), data) - self.assertTrue("token" in response.data["d"]) + self.assertTrue("token" in response.data or "token" in response.data["d"]) def test_password_reset_bad_token(self): data = { From 1431f7b49d934b50ee1d1c538e591e314cc24c64 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 00:38:00 +0100 Subject: [PATCH 158/185] 100% coverage on leaderboard views and challenge mixin --- src/challenge/tests/mixins.py | 2 +- src/leaderboard/tests.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/challenge/tests/mixins.py b/src/challenge/tests/mixins.py index 8cbe208f..1cc46bff 100644 --- a/src/challenge/tests/mixins.py +++ b/src/challenge/tests/mixins.py @@ -67,7 +67,7 @@ def setUp(self) -> None: self.user3.team = self.team2 self.user3.save() - def find_challenge_entry(self, challenge: "Challenge", data: Union[dict[str, list[dict]], list[dict]]) -> Optional[dict]: + def find_challenge_entry(self, challenge: "Challenge", data: Union[dict[str, list[dict]], list[dict]]) -> Optional[dict]: # pragma: no cover """Get the relevant serialized JSON for a specified challenge.""" challenges = [] if type(data) is list: diff --git a/src/leaderboard/tests.py b/src/leaderboard/tests.py index d8569687..30d03322 100644 --- a/src/leaderboard/tests.py +++ b/src/leaderboard/tests.py @@ -103,6 +103,13 @@ def test_user_only(self): self.assertEqual(response.data["d"]["user"][0]["points"], 1400) self.assertNotIn("team", response.data["d"].keys()) + def test_caching(self): + config.set("enable_caching", True) + uncached_response = self.client.get(reverse("leaderboard-graph")) + cached_response = self.client.get(reverse("leaderboard-graph")) + config.set("enable_caching", False) + self.assertEqual(uncached_response.data, cached_response.data) + class UserListTestCase(APITestCase): def setUp(self): From 09f7d8f463001a14feef30f0f1b4cc86fa6efc8e Mon Sep 17 00:00:00 2001 From: Jeremiah Boby Date: Wed, 9 Jun 2021 02:03:29 +0100 Subject: [PATCH 159/185] Add querycount middleware --- src/backend/settings/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py index aad91922..a6407faf 100644 --- a/src/backend/settings/__init__.py +++ b/src/backend/settings/__init__.py @@ -130,6 +130,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "django_prometheus.middleware.PrometheusAfterMiddleware", + "querycount.middleware.QueryCountMiddleware", ] if DEBUG: MIDDLEWARE.insert(0, "better_exceptions.integrations.django.BetterExceptionsMiddleware") From 8214f2dd170624cd3e5d84894a888a669025676e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 00:41:21 +0100 Subject: [PATCH 160/185] no cover plugin loading --- src/plugins/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/apps.py b/src/plugins/apps.py index c6b64af8..09cc385e 100644 --- a/src/plugins/apps.py +++ b/src/plugins/apps.py @@ -12,6 +12,6 @@ class PluginConfig(AppConfig, abc.ABC): def ready(self): from plugins import providers - if hasattr(self, "provides") and isinstance(self.provides, list): + if hasattr(self, "provides") and isinstance(self.provides, list): # pragma: no cover for provider in map(locate, self.provides): providers.register_provider(provider.type, provider()) From 9402285e411a863f1eb1d8543c0278b0d4b1a5be Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:13:05 +0100 Subject: [PATCH 161/185] fix vote counts --- src/challenge/tests/test_utils.py | 11 +++++++++++ src/challenge/tests/test_views.py | 21 ++++++++++++++++++++- src/challenge/views.py | 16 +++++++--------- 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 src/challenge/tests/test_utils.py diff --git a/src/challenge/tests/test_utils.py b/src/challenge/tests/test_utils.py new file mode 100644 index 00000000..9f941d29 --- /dev/null +++ b/src/challenge/tests/test_utils.py @@ -0,0 +1,11 @@ +from unittest import TestCase + +from django.contrib.auth import get_user_model + +from challenge.views import get_cache_key + + +class CacheKeyTestCase(TestCase): + def test_get_cache_key_no_team(self): + user = get_user_model()(username="cachekeytest", email="cachekeytest@example.com") + self.assertTrue(get_cache_key(user).endswith("no_team")) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 27eb9636..172f2288 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -175,16 +175,27 @@ def test_category_list_challenge_redacting(self): self.user.save() self.client.force_authenticate(self.user) response = self.client.get(reverse("categories-list")) - print(self.find_challenge_entry(self.challenge1, data=response.data)) + config.set("enable_caching", False) self.assertFalse("description" in self.find_challenge_entry(self.challenge1, data=response.data)) def test_category_list_challenge_redacting_admin(self): self.user.is_staff = True self.user.save() self.client.force_authenticate(self.user) + config.set("enable_caching", False) response = self.client.get(reverse("categories-list")) self.assertTrue("description" in self.find_challenge_entry(self.challenge3, data=response.data)) + def test_category_list_challenge_redacting_admin_denied(self): + self.user.is_staff = True + self.user.save() + self.client.force_authenticate(self.user) + config.set("enable_caching", False) + config.set("enable_force_admin_2fa", True) + response = self.client.get(reverse("categories-list")) + config.set("enable_force_admin_2fa", False) + self.assertEqual(response.data["d"], []) + def test_category_list_challenge_unlocked_admin(self): self.user.is_staff = True self.user.save() @@ -220,6 +231,14 @@ def test_category_create_unauthorized(self): ) self.assertTrue(response.status_code, HTTP_403_FORBIDDEN) + def test_category_list_content_cached(self): + self.client.force_authenticate(self.user) + config.set("enable_caching", True) + uncached_response = self.client.get(reverse("categories-list")) + cached_response = self.client.get(reverse("categories-list")) + config.set("enable_caching", False) + self.assertEqual(uncached_response.data, cached_response.data) + class ChallengeViewsetTestCase(ChallengeSetupMixin, APITestCase): def test_challenge_list_unauthenticated_permission(self): diff --git a/src/challenge/views.py b/src/challenge/views.py index a8dde22e..7fe6cafa 100644 --- a/src/challenge/views.py +++ b/src/challenge/views.py @@ -106,21 +106,19 @@ def get_queryset(self): def list(self, request, *args, **kwargs): cache = caches["default"] categories = cache.get(get_cache_key(request.user)) - cache_hit = categories is not None if categories is None or not config.get("enable_caching"): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) categories = serializer.data cache.set(get_cache_key(request.user), categories, 3600) - if cache_hit: - solve_counts = get_solve_counts() - positive_votes = get_positive_votes() - negative_votes = get_negative_votes() - for category in categories: - for challenge in category["challenges"]: - challenge["votes"] = {"positive": positive_votes.get(challenge["id"], 0), "negative": negative_votes.get(challenge["id"], 0)} - challenge["solve_count"] = solve_counts.get(challenge["id"], 0) + solve_counts = get_solve_counts() + positive_votes = get_positive_votes() + negative_votes = get_negative_votes() + for category in categories: + for challenge in category["challenges"]: + challenge["votes"] = {"positive": positive_votes.get(challenge["id"], 0), "negative": negative_votes.get(challenge["id"], 0)} + challenge["solve_count"] = solve_counts.get(challenge["id"], 0) return FormattedResponse(categories) From 7ca30da8d16cf01d238eb15db855adeed9570208 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:16:49 +0100 Subject: [PATCH 162/185] more flag submit tests --- src/challenge/tests/test_views.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 172f2288..069a3cfc 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -50,6 +50,25 @@ def test_challenge_double_solve(self): response = self.client.post(reverse("submit-flag"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + def solve_challenge_not_unlocked(self): + self.client.force_authenticate(user=self.user) + data = { + "flag": "ractf{a}", + "challenge": self.challenge3.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def solve_challenge_attempt_limit_reached(self): + self.client.force_authenticate(user=self.user) + self.challenge3.challenge_metadata = {"attempt_limit": -1} + data = { + "flag": "ractf{a}", + "challenge": self.challenge3.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertEqual(response.data["m"], "attempt_limit_reached") + def test_challenge_unlocks(self): self.solve_challenge() self.challenge1.unlock_requirements = str(self.challenge2.id) From 21f59629514b8448d13c93cbdae055cdb93cccf5 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:32:27 +0100 Subject: [PATCH 163/185] Flag submit tests --- src/challenge/tests/test_views.py | 35 +++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index 069a3cfc..b99cc30d 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -50,7 +50,7 @@ def test_challenge_double_solve(self): response = self.client.post(reverse("submit-flag"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - def solve_challenge_not_unlocked(self): + def test_solve_challenge_not_unlocked(self): self.client.force_authenticate(user=self.user) data = { "flag": "ractf{a}", @@ -59,16 +59,43 @@ def solve_challenge_not_unlocked(self): response = self.client.post(reverse("submit-flag"), data) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) - def solve_challenge_attempt_limit_reached(self): + def test_solve_challenge_attempt_limit_reached(self): self.client.force_authenticate(user=self.user) - self.challenge3.challenge_metadata = {"attempt_limit": -1} + self.challenge2.challenge_metadata = {"attempt_limit": -1} + self.challenge2.save() data = { "flag": "ractf{a}", - "challenge": self.challenge3.id, + "challenge": self.challenge2.id, } response = self.client.post(reverse("submit-flag"), data) + self.challenge2.challenge_metadata = {} + self.challenge2.save() self.assertEqual(response.data["m"], "attempt_limit_reached") + def test_solve_challenge_attempt_limit_not_reached(self): + self.client.force_authenticate(user=self.user) + self.challenge2.challenge_metadata = {"attempt_limit": 5000} + self.challenge2.save() + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.challenge2.challenge_metadata = {} + self.challenge2.save() + self.assertNotEqual(response.data["m"], "attempt_limit_reached") + + def test_solve_challenge_with_explanation(self): + self.client.force_authenticate(user=self.user) + self.challenge2.post_score_explanation = "test" + self.challenge2.save() + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + response = self.client.post(reverse("submit-flag"), data) + self.assertTrue("explanation" in response.data["d"]) + def test_challenge_unlocks(self): self.solve_challenge() self.challenge1.unlock_requirements = str(self.challenge2.id) From 6f59d271a51d25ede8a1783daae2226b13dfdfe0 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:41:19 +0100 Subject: [PATCH 164/185] sql tests and bugfix --- src/challenge/sql.py | 4 ++-- src/challenge/tests/test_utils.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/challenge/sql.py b/src/challenge/sql.py index 4853dc23..8f77b3f6 100644 --- a/src/challenge/sql.py +++ b/src/challenge/sql.py @@ -36,7 +36,7 @@ def get_positive_votes(): with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=true GROUP BY challenge_id;") positive_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set("positive_votes", cache.get("positive_votes"), 15) + cache.set("positive_votes", positive_votes, 15) return positive_votes @@ -48,5 +48,5 @@ def get_negative_votes(): with connection.cursor() as cursor: cursor.execute("SELECT challenge_id, COUNT(*) FROM challenge_challengevote WHERE positive=false GROUP BY challenge_id;") negative_votes = {i[0]: i[1] for i in cursor.fetchall()} - cache.set("negative_votes", cache.get("negative_votes"), 15) + cache.set("negative_votes", negative_votes, 15) return negative_votes diff --git a/src/challenge/tests/test_utils.py b/src/challenge/tests/test_utils.py index 9f941d29..2cb0df3f 100644 --- a/src/challenge/tests/test_utils.py +++ b/src/challenge/tests/test_utils.py @@ -1,11 +1,30 @@ from unittest import TestCase from django.contrib.auth import get_user_model +from rest_framework.test import APITestCase +from challenge.sql import get_negative_votes, get_positive_votes from challenge.views import get_cache_key +from config import config class CacheKeyTestCase(TestCase): def test_get_cache_key_no_team(self): user = get_user_model()(username="cachekeytest", email="cachekeytest@example.com") self.assertTrue(get_cache_key(user).endswith("no_team")) + + +class SqlTestCase(APITestCase): + def test_get_positive_votes_cached(self): + config.set("enable_caching", True) + first = get_positive_votes() + second = get_positive_votes() + config.set("enable_caching", False) + self.assertEqual(first, second) + + def test_get_negative_votes_cached(self): + config.set("enable_caching", True) + first = get_negative_votes() + second = get_negative_votes() + config.set("enable_caching", False) + self.assertEqual(first, second) From 096ff8959dacf2a9a2382fed6010bee684fb9809 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:47:21 +0100 Subject: [PATCH 165/185] remove iscompetitionopen - unused --- src/backend/permissions.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/backend/permissions.py b/src/backend/permissions.py index a6755c82..9d341040 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -1,9 +1,5 @@ -import time - from rest_framework import permissions -from config import config - class AdminOrReadOnlyVisible(permissions.BasePermission): def has_object_permission(self, request, view, obj): @@ -26,11 +22,6 @@ def has_permission(self, request, view): return True -class IsCompetitionOpen(permissions.BasePermission): - def has_permission(self, request, view): - return config.get("start_time") <= time.time() - - class IsBot(permissions.BasePermission): def has_permission(self, request, view): return request.user.is_authenticated and request.user.is_bot From 8de3ef42b00497841bbfac421d044659c155c69d Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:52:50 +0100 Subject: [PATCH 166/185] test permissions --- src/backend/permissions.py | 2 +- src/backend/tests.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/backend/permissions.py b/src/backend/permissions.py index 9d341040..3d05be55 100644 --- a/src/backend/permissions.py +++ b/src/backend/permissions.py @@ -30,7 +30,7 @@ def has_permission(self, request, view): class ReadOnlyBot(permissions.BasePermission): def has_permission(self, request, view): if request.user.is_authenticated and request.user.is_bot: - return request.method not in permissions.SAFE_METHODS + return request.method in permissions.SAFE_METHODS return True diff --git a/src/backend/tests.py b/src/backend/tests.py index 04e959d6..3e559689 100644 --- a/src/backend/tests.py +++ b/src/backend/tests.py @@ -1,8 +1,27 @@ +from django.http import HttpRequest +from rest_framework.request import Request from rest_framework.status import HTTP_404_NOT_FOUND from rest_framework.test import APITestCase +from backend.permissions import ReadOnlyBot +from member.models import Member + class CatchAllTestCase(APITestCase): def test_catchall_404s(self): response = self.client.get("/sdgodgsjds") self.assertEqual(response.status_code, HTTP_404_NOT_FOUND) + + +class ReadOnlyBotTestCase(APITestCase): + def test_is_bot_safe_method(self): + request = Request(HttpRequest()) + request.method = "GET" + request.user = Member(username="bot-test", email="bot-test@gmail.com", is_bot=True) + self.assertTrue(ReadOnlyBot().has_permission(request, None)) + + def test_is_bot_unsafe_method(self): + request = Request(HttpRequest()) + request.method = "POST" + request.user = Member(username="bot-test", email="bot-test@gmail.com", is_bot=True) + self.assertFalse(ReadOnlyBot().has_permission(request, None)) From 222b57187387b2fa37c4555cd3f85647dd30701f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 01:58:15 +0100 Subject: [PATCH 167/185] test printable_name --- src/backend/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/backend/tests.py b/src/backend/tests.py index 3e559689..c7e55df0 100644 --- a/src/backend/tests.py +++ b/src/backend/tests.py @@ -1,9 +1,11 @@ +from django.core.exceptions import ValidationError from django.http import HttpRequest from rest_framework.request import Request from rest_framework.status import HTTP_404_NOT_FOUND from rest_framework.test import APITestCase from backend.permissions import ReadOnlyBot +from backend.validators import printable_name from member.models import Member @@ -25,3 +27,11 @@ def test_is_bot_unsafe_method(self): request.method = "POST" request.user = Member(username="bot-test", email="bot-test@gmail.com", is_bot=True) self.assertFalse(ReadOnlyBot().has_permission(request, None)) + + +class ValidatorTestCase(APITestCase): + def test_unprintable_name(self): + self.assertRaises(ValidationError, lambda: printable_name(b"\x00".decode("latin-1"))) + + def test_printable_name(self): + self.assertIsNone(printable_name("abc")) From 3424bcfa9006bbc60a167203f3ef4a16edbcc4ba Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:18:07 +0100 Subject: [PATCH 168/185] fix accepted_renderer terminal spam --- src/backend/exception_handler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/exception_handler.py b/src/backend/exception_handler.py index 57567ccd..47bf2b2f 100644 --- a/src/backend/exception_handler.py +++ b/src/backend/exception_handler.py @@ -2,7 +2,7 @@ from typing import Optional from django.conf import settings -from django.http import Http404, HttpRequest +from django.http import Http404, HttpRequest, JsonResponse from rest_framework import exceptions from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response @@ -51,6 +51,6 @@ def handle_exception(exc: Exception, context: dict) -> Optional[Response]: return response -def generic_error_response(request: HttpRequest, *args, **kwargs) -> Response: +def generic_error_response(request: HttpRequest, *args, **kwargs) -> JsonResponse: """Return a generic error response for unexpected errors.""" - return Response({"s": False, "m": "Internal server error.", "d": ""}, status=HTTP_500_INTERNAL_SERVER_ERROR) + return JsonResponse({"s": False, "m": "Internal server error.", "d": ""}, status=HTTP_500_INTERNAL_SERVER_ERROR) From 6957a81dd51c0ce44dedf9cd0fffb162e362739d Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:21:31 +0100 Subject: [PATCH 169/185] config bugfixes and tests --- src/config/tests.py | 10 ++++++++++ src/config/views.py | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/config/tests.py b/src/config/tests.py index 1ce760f4..89512ba6 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -35,6 +35,16 @@ def test_auth_authed_staff(self): response = self.client.get(reverse("config-list")) self.assertEqual(response.status_code, HTTP_200_OK) + def test_get_sensitive_not_staff(self): + self.client.force_authenticate(self.user) + response = self.client.get(reverse("config-pk", kwargs={"name": "enable_force_admin_2fa"})) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_get_sensitive_staff(self): + self.client.force_authenticate(self.staff_user) + response = self.client.get(reverse("config-pk", kwargs={"name": "enable_force_admin_2fa"})) + self.assertEqual(response.status_code, HTTP_200_OK) + def test_post_authed(self): self.client.force_authenticate(self.user) response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"key": "test", "value": "test"}, format="json") diff --git a/src/config/views.py b/src/config/views.py index 99f1cf7b..b976135e 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -17,10 +17,10 @@ class ConfigView(APIView): def get(self, request, name=None): if name is None: - if request.user.is_superuser: + if request.user.is_staff: return FormattedResponse(config.get_all()) return FormattedResponse(config.get_all_non_sensitive()) - if not config.is_sensitive(name) or request.is_superuser: + if not config.is_sensitive(name) or request.user.is_staff: return FormattedResponse(config.get(name)) return FormattedResponse(status=HTTP_403_FORBIDDEN) From 2d6efc9af0810bfcd499417ca1ef970564083f69 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:24:25 +0100 Subject: [PATCH 170/185] config post test coverage --- src/config/tests.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config/tests.py b/src/config/tests.py index 89512ba6..cd41ba98 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -4,6 +4,7 @@ HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, + HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, ) from rest_framework.test import APITestCase @@ -55,9 +56,15 @@ def test_create(self): response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") self.assertEqual(response.status_code, HTTP_201_CREATED) - def test_update(self): + def test_update_post(self): self.client.force_authenticate(self.staff_user) self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) self.assertEqual(config.get("test"), "test2") + + def test_update_post_bad_request(self): + self.client.force_authenticate(self.staff_user) + self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") + response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) From 8a429f6df8cce657ccc2fa682e6e15eaaf66c119 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:38:47 +0100 Subject: [PATCH 171/185] remove unused password_reset_token --- .../0008_remove_member_password_reset_token.py | 17 +++++++++++++++++ src/member/models.py | 1 - src/member/serializers.py | 1 - src/member/tests.py | 2 -- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 src/member/migrations/0008_remove_member_password_reset_token.py diff --git a/src/member/migrations/0008_remove_member_password_reset_token.py b/src/member/migrations/0008_remove_member_password_reset_token.py new file mode 100644 index 00000000..c783ceeb --- /dev/null +++ b/src/member/migrations/0008_remove_member_password_reset_token.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.4 on 2021-06-09 01:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0007_auto_20201226_1330'), + ] + + operations = [ + migrations.RemoveField( + model_name='member', + name='password_reset_token', + ), + ] diff --git a/src/member/models.py b/src/member/models.py index 9aa9fb01..33b34f54 100644 --- a/src/member/models.py +++ b/src/member/models.py @@ -45,7 +45,6 @@ class Member(ExportModelOperationsMixin("member"), AbstractUser): team = models.ForeignKey("team.Team", on_delete=SET_NULL, null=True, related_name="members") email_verified = models.BooleanField(default=False) email_token = models.CharField(max_length=64, default=secrets.token_hex) - password_reset_token = models.CharField(max_length=64, default=secrets.token_hex) points = models.IntegerField(default=0) leaderboard_points = models.IntegerField(default=0) last_score = models.DateTimeField(default=timezone.now) diff --git a/src/member/serializers.py b/src/member/serializers.py index 7092ea6c..5a6c63c5 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -140,7 +140,6 @@ class Meta: read_only_fields = ["id", "is_staff", "team", "points", "leaderboard_points", "date_joined", "incorrect_solves", "is_verified"] def validate_email(self, value): - self.instance.password_reset_token = secrets.token_hex() self.instance.email_token = secrets.token_hex() self.instance.save() return value diff --git a/src/member/tests.py b/src/member/tests.py index 4bad414a..74c0a131 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -40,12 +40,10 @@ def test_self_change_email_invalid(self): self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_self_change_email_token_change(self): - pr_token = self.user.password_reset_token ev_token = self.user.email_token self.client.force_authenticate(self.user) self.client.put(reverse("member-self"), data={"email": "test-self3@example.org"}) user = get_user_model().objects.get(id=self.user.id) - self.assertNotEqual(pr_token, user.password_reset_token) self.assertNotEqual(ev_token, user.email_token) def test_self_get_email(self): From dfce5f35cfc7d59407e6f2de59b821ed674fc5d3 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:43:02 +0100 Subject: [PATCH 172/185] remove databasebackend - unused since we converted the old RedisBackend to use the django cache api --- src/config/backends.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/config/backends.py b/src/config/backends.py index e7624691..fded1945 100644 --- a/src/config/backends.py +++ b/src/config/backends.py @@ -28,25 +28,6 @@ def save(self): pass -class DatabaseBackend(ConfigBackend): - """Only use this if you absolutely have to""" - - def get(self, key): - value = Config.objects.get(key=key).value["value"] - return value - - def set(self, key, value): - setting = Config.objects.get(key=key) - setting.value["value"] = value - setting.save() - - def get_all(self): - config = {} - for item in Config.objects.all(): - config[item.key] = item.value["value"] - return config - - class CachedBackend(ConfigBackend): @property def config_set(self) -> "QuerySet[Config]": From 780debe649843c4eca1b62a3d98e066f959331f4 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:45:06 +0100 Subject: [PATCH 173/185] clean up test_password_reset_issues_token --- src/authentication/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authentication/tests.py b/src/authentication/tests.py index e9286188..78306e19 100644 --- a/src/authentication/tests.py +++ b/src/authentication/tests.py @@ -676,7 +676,7 @@ def test_password_reset_issues_token(self): "password": "uO7*$E@0ngqL", } response = self.client.post(reverse("do-password-reset"), data) - self.assertTrue("token" in response.data or "token" in response.data["d"]) + self.assertTrue("token" in response.data["d"]) def test_password_reset_bad_token(self): data = { From fe9d6933e690d4583c70fc7df21f9fc7604c420a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:49:19 +0100 Subject: [PATCH 174/185] remove a couple instances of is_superuser, replacing with is_staff --- src/authentication/basic_auth.py | 2 +- src/team/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/authentication/basic_auth.py b/src/authentication/basic_auth.py index 4ccb9a72..04a83386 100644 --- a/src/authentication/basic_auth.py +++ b/src/authentication/basic_auth.py @@ -41,7 +41,7 @@ def login_user(self, username, password, context, **kwargs): login_reject.send(sender=self.__class__, username=username, reason="creds") raise FormattedException(m="incorrect_username_or_password", d={"reason": "incorrect_username_or_password"}, status=HTTP_401_UNAUTHORIZED) - if not user.email_verified and not user.is_superuser: + if not user.email_verified and not user.is_staff: login_reject.send(sender=self.__class__, username=username, reason="email") raise FormattedException(m="email_verification_required", d={"reason": "email_verification_required"}, status=HTTP_401_UNAUTHORIZED) diff --git a/src/team/views.py b/src/team/views.py index f569d7af..bcdd5f12 100644 --- a/src/team/views.py +++ b/src/team/views.py @@ -67,10 +67,10 @@ class TeamViewSet(AdminListModelViewSet): def get_queryset(self): if self.action == "list": - if self.request.user.is_superuser: + if self.request.user.is_staff: return Team.objects.order_by("id").prefetch_related("members") return Team.objects.filter(is_visible=True).order_by("id").prefetch_related("members") - if self.request.user.is_superuser and not self.request.user.should_deny_admin(): + if self.request.user.is_staff and not self.request.user.should_deny_admin(): return Team.objects.order_by("id").prefetch_related( "solves", "members", From 32c3d76539f8713015b497f2004415e9152e2d09 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:56:54 +0100 Subject: [PATCH 175/185] test username changing --- src/member/serializers.py | 1 + src/member/tests.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/member/serializers.py b/src/member/serializers.py index 5a6c63c5..6251b688 100644 --- a/src/member/serializers.py +++ b/src/member/serializers.py @@ -148,6 +148,7 @@ def update(self, instance, validated_data): if not config.get("enable_teams"): if instance.team: instance.team.name = validated_data.get("username", instance.username) + instance.team.save() return super(SelfSerializer, self).update(instance, validated_data) diff --git a/src/member/tests.py b/src/member/tests.py index 74c0a131..b71eaac5 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -8,6 +8,9 @@ ) from rest_framework.test import APITestCase +from config import config +from team.models import Team + class MemberTestCase(APITestCase): def setUp(self): @@ -51,6 +54,17 @@ def test_self_get_email(self): response = self.client.get(reverse("member-self")) self.assertEqual(response.data["email"], "test-self@example.org") + def test_self_change_username_teams_disabled(self): + self.client.force_authenticate(self.user) + team = Team(name="team", password="123", owner=self.user) + team.save() + self.user.team = team + self.user.save() + config.set("enable_teams", False) + self.client.put(reverse("member-self"), data={"username": "test-self2", "email": "test-self@example.org"}) + config.set("enable_teams", True) + self.assertEqual(Team.objects.get(id=team.id).name, "test-self2") + class MemberViewSetTestCase(APITestCase): def setUp(self): From 0a8ae63d8d2afaf445596b6b005a1c9266055c1a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 02:58:08 +0100 Subject: [PATCH 176/185] test username changing --- src/member/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/member/tests.py b/src/member/tests.py index b71eaac5..aca52257 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -65,6 +65,13 @@ def test_self_change_username_teams_disabled(self): config.set("enable_teams", True) self.assertEqual(Team.objects.get(id=team.id).name, "test-self2") + def test_self_change_username_no_team(self): + self.client.force_authenticate(self.user) + config.set("enable_teams", False) + self.client.put(reverse("member-self"), data={"username": "test-self2", "email": "test-self@example.org"}) + config.set("enable_teams", True) + self.assertEqual(get_user_model().objects.get(id=self.user.id).username, "test-self2") + class MemberViewSetTestCase(APITestCase): def setUp(self): From e5bb1e095c7b3efc24fb26ec66cae5129bde4dee Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:09:09 +0100 Subject: [PATCH 177/185] test userip --- src/member/tests.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/member/tests.py b/src/member/tests.py index aca52257..5300729c 100644 --- a/src/member/tests.py +++ b/src/member/tests.py @@ -1,4 +1,7 @@ from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser +from django.http import HttpRequest +from rest_framework.request import Request from rest_framework.reverse import reverse from rest_framework.status import ( HTTP_200_OK, @@ -9,6 +12,7 @@ from rest_framework.test import APITestCase from config import config +from member.models import UserIP from team.models import Team @@ -149,3 +153,31 @@ def test_patch_member_admin(self): data={"username": "test"}, ) self.assertEqual(response.status_code, HTTP_200_OK) + + +class UserIPTest(APITestCase): + def test_not_authenticated(self): + request = Request(HttpRequest()) + request.user = AnonymousUser() + self.assertNumQueries(0, lambda: UserIP.hook(request)) + + def test_first_sight(self): + request = Request(HttpRequest()) + user = get_user_model()(username="test-userip", email="test-userip@example.org") + user.save() + request.user = user + request.META["x-forward-for"] = "1.1.1.1" + request.META["user-agent"] = "test" + UserIP.hook(request) + self.assertEqual(UserIP.objects.get(user=user).seen, 1) + + def test_second_sight(self): + request = Request(HttpRequest()) + user = get_user_model()(username="test-userip2", email="test-userip2@example.org") + user.save() + request.user = user + request.META["x-forward-for"] = "1.1.1.1" + request.META["user-agent"] = "test" + UserIP.hook(request) + UserIP.hook(request) + self.assertEqual(UserIP.objects.get(user=user).seen, 2) From 9f2bd9294e295c09e415d4511a2b78d30a8291bd Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:12:15 +0100 Subject: [PATCH 178/185] no cover stats migration --- src/stats/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/apps.py b/src/stats/apps.py index e60fca45..3b2580c0 100644 --- a/src/stats/apps.py +++ b/src/stats/apps.py @@ -14,7 +14,7 @@ class StatsConfig(AppConfig): def ready(self): """Logic for adding extra prometheus statistics.""" - if "migrate" in sys.argv or "makemigrations" in sys.argv: + if "migrate" in sys.argv or "makemigrations" in sys.argv: # pragma: no cover # Don't run stats-related logic if we haven't migrated yet return From b3588d21d935e0a168e471473f36d46b4ffdd8aa Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:16:08 +0100 Subject: [PATCH 179/185] Remove logic for no team users from the base points plugin Its no longer possible for a user to not have a team, if teams are disabled the user will have a team only containing themself --- src/plugins/points/base.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/points/base.py b/src/plugins/points/base.py index 1af7ec07..677b6934 100644 --- a/src/plugins/points/base.py +++ b/src/plugins/points/base.py @@ -27,26 +27,26 @@ def recalculate(self, teams, users, solves, *args, **kwargs): def score(self, user, team, flag, solves, *args, **kwargs): challenge = self.challenge points = self.get_points(team, flag, solves.count()) - if team is not None: - deducted = HintUse.objects.filter(team=team, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) - else: - deducted = HintUse.objects.filter(user=user, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) + + deducted = HintUse.objects.filter(team=team, challenge=challenge).aggregate(points=Sum(F("hint__penalty"))) deducted = 0 if deducted["points"] is None else deducted["points"] deducted = min(points, deducted) + scored = config.get("end_time") >= time.time() and config.get("enable_scoring") score = Score(team=team, reason="challenge", points=points, penalty=deducted, leaderboard=scored, user=user) score.save() + solve = Solve(team=team, solved_by=user, challenge=challenge, first_blood=challenge.first_blood is None, flag=flag, score=score) solve.save() + user.points += points - deducted - if team is not None: - team.points += points - deducted + team.points += points - deducted if scored: user.leaderboard_points += points - deducted - if team is not None: - team.leaderboard_points += points - deducted + team.leaderboard_points += points - deducted user.last_score = timezone.now() team.last_score = timezone.now() + return solve def register_incorrect_attempt(self, user, team, flag, solves, *args, **kwargs): From bb0e30054a4233be41c342e1cfe262ed5445dd51 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:22:29 +0100 Subject: [PATCH 180/185] test incorrect solve tracking being disabled --- src/plugins/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/tests.py b/src/plugins/tests.py index c94af96f..ad7e4c3b 100644 --- a/src/plugins/tests.py +++ b/src/plugins/tests.py @@ -225,3 +225,10 @@ def test_plugin_loader(self): # TODO: why is this loading 5 # self.assertEqual(len(plugins.plugins['flag']), 4) self.assertEqual(len(plugins.plugins["points"]), 2) + + +class BasePluginTest(ChallengeSetupMixin, APITestCase): + def test_dont_track_incorrect_submissions(self): + config.set("enable_track_incorrect_submissions", False) + plugin = BasicPointsPlugin(self.challenge2) + self.assertNumQueries(0, lambda: plugin.register_incorrect_attempt(self.user, self.team, "ractf{}", None)) From 3d5e62d901ca848d527b81f91d5cabc25003d8f4 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:25:18 +0100 Subject: [PATCH 181/185] Make config post tests actually test post --- src/config/tests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/config/tests.py b/src/config/tests.py index cd41ba98..5015afb8 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -3,7 +3,6 @@ from rest_framework.status import ( HTTP_200_OK, HTTP_201_CREATED, - HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, ) @@ -59,12 +58,11 @@ def test_create(self): def test_update_post(self): self.client.force_authenticate(self.staff_user) self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") - response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") - self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) + self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") self.assertEqual(config.get("test"), "test2") def test_update_post_bad_request(self): self.client.force_authenticate(self.staff_user) self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") - response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") + response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) From a6069d33dcb64087d010ae25c095821b499dd2cc Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:31:56 +0100 Subject: [PATCH 182/185] fix config --- src/config/tests.py | 18 ++++++++++++++++++ src/config/views.py | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/config/tests.py b/src/config/tests.py index 5015afb8..d1737f13 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -66,3 +66,21 @@ def test_update_post_bad_request(self): self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) + + def test_update_patch(self): + self.client.force_authenticate(self.staff_user) + self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") + self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test2"}, format="json") + self.assertEqual(config.get("test"), "test2") + + def test_update_patch_bad_request(self): + self.client.force_authenticate(self.staff_user) + self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") + response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) + + def test_update_patch_list(self): + self.client.force_authenticate(self.staff_user) + config.set("testlist", ["test"]) + self.client.patch(reverse("config-pk", kwargs={"name": "testlist"}), data={"value": "test"}, format="json") + self.assertEqual(config.get("testlist"), ["test", "test"]) diff --git a/src/config/views.py b/src/config/views.py index b976135e..00652343 100644 --- a/src/config/views.py +++ b/src/config/views.py @@ -34,7 +34,9 @@ def patch(self, request, name): if "value" not in request.data: return FormattedResponse(status=HTTP_400_BAD_REQUEST) if config.get(name) is not None and isinstance(config.get(name), list): - config.set("name", config.get(name).append(request.data["value"])) + value = config.get(name) + value.append(request.data["value"]) + config.set(name, value) return FormattedResponse() config.set(name, request.data.get("value")) return FormattedResponse(status=HTTP_204_NO_CONTENT) From 9cbda2ee9100410f3702d26b4b7331dd588f52e8 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:33:40 +0100 Subject: [PATCH 183/185] fix config --- src/config/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/tests.py b/src/config/tests.py index d1737f13..40db61ac 100644 --- a/src/config/tests.py +++ b/src/config/tests.py @@ -75,8 +75,8 @@ def test_update_patch(self): def test_update_patch_bad_request(self): self.client.force_authenticate(self.staff_user) - self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") - response = self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") + self.client.post(reverse("config-pk", kwargs={"name": "test"}), data={"value": "test"}, format="json") + response = self.client.patch(reverse("config-pk", kwargs={"name": "test"}), data={}, format="json") self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) def test_update_patch_list(self): From 4d42397ffc2fb89439a93c5677fd82e65ea5d6bd Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:55:31 +0100 Subject: [PATCH 184/185] test flag check view --- src/challenge/tests/test_views.py | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/challenge/tests/test_views.py b/src/challenge/tests/test_views.py index b99cc30d..b8b5f6e8 100644 --- a/src/challenge/tests/test_views.py +++ b/src/challenge/tests/test_views.py @@ -406,3 +406,66 @@ def test_create_challenge_unauthorized(self): format="json", ) self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + +class FlagCheckViewTestCase(ChallengeSetupMixin, APITestCase): + def test_disable_flag_submission(self): + self.client.force_authenticate(self.user) + config.set("enable_flag_submission", False) + response = self.client.post(reverse("check-flag")) + config.set("enable_flag_submission", True) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_bad_request(self): + self.client.force_authenticate(self.user) + response = self.client.post(reverse("check-flag")) + self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) + + def test_havent_solved_challenge(self): + self.client.force_authenticate(self.user) + response = self.client.post( + reverse("check-flag"), + data={ + "challenge": self.challenge1.id, + "flag": "a", + }, + ) + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) + + def test_incorrect_flag(self): + self.client.force_authenticate(self.user) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + self.client.post(reverse("submit-flag"), data) + response = self.client.post( + reverse("check-flag"), + data={ + "challenge": self.challenge2.id, + "flag": "a", + }, + ) + self.assertEqual(response.data["m"], "incorrect_flag") + + def test_correct_flag(self): + self.client.force_authenticate(self.user) + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + self.client.post(reverse("submit-flag"), data) + response = self.client.post(reverse("check-flag"), data) + self.assertEqual(response.data["m"], "correct_flag") + + def test_post_score_explanation(self): + self.client.force_authenticate(self.user) + self.challenge2.post_score_explanation = "test" + self.challenge2.save() + data = { + "flag": "ractf{a}", + "challenge": self.challenge2.id, + } + self.client.post(reverse("submit-flag"), data) + response = self.client.post(reverse("check-flag"), data) + self.assertTrue("explanation" in response.data["d"]) From a6cac865d562385a3583bdf6ee6c67e02e67ea69 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Jun 2021 03:56:07 +0100 Subject: [PATCH 185/185] remove deprecated and implict cli flag from isort --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7ad9b7e2..d8e03da4 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ coverage: xdg-open htmlcov/index.html format: - isort -rc src && \ + isort src && \ black src lint: