Skip to content

Commit

Permalink
Definitive project formatting (#80)
Browse files Browse the repository at this point in the history
* Applied `black` to `ctfpad`

* Applied `black` to `ctftools`

* Applied `djhtml` to `ctfpad/templates`

* Applied `djcss` to `ctfpad/static/css`

* Applied `djjs` to `static/js`

* Using `pre-commit` for automatic formatting

* Enforcing black version in `pre-commit` config file

* [ci] added a workflow for validating formatting
  • Loading branch information
hugsy authored Jun 24, 2023
1 parent 5bdacd2 commit a040458
Show file tree
Hide file tree
Showing 88 changed files with 4,340 additions and 2,089 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Format Validation

on:
pull_request:
push:
branches: [main]

jobs:
format-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: pre-commit/[email protected]
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/rtts/djhtml
rev: '3.0.6'
hooks:
- id: djhtml
files: ctfpad/templates/.*\.html$

- id: djcss
files: static/css/.*\.css$

- id: djjs
files: static/js/.*\.js$

- repo: https://github.com/psf/black
rev: '23.3.0'
hooks:
- id: black
language_version: python3.10
2 changes: 1 addition & 1 deletion ctfpad/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
default_app_config = 'ctfpad.apps.CtfpadConfig'
default_app_config = "ctfpad.apps.CtfpadConfig"
4 changes: 2 additions & 2 deletions ctfpad/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@


class CtfpadConfig(AppConfig):
name = 'ctfpad'
verbose_name = _('ctfpad')
name = "ctfpad"
verbose_name = _("ctfpad")

def ready(self):
import ctfpad.signals
1 change: 1 addition & 0 deletions ctfpad/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import django.http
from django.conf import settings


def add_debug_context(request: django.http.HttpRequest) -> dict:
return {
"DEBUG": settings.DEBUG,
Expand Down
6 changes: 5 additions & 1 deletion ctfpad/decorators/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ def is_authenticated(view_func):
def wrapper_func(request: HttpRequest, *args, **kwargs):
if not request.user.is_authenticated:
messages.warning(request, "You must be authenticated!")
return redirect(reverse("ctfpad:user-login") + "?" + urlencode({"redirect_to": request.path}))
return redirect(
reverse("ctfpad:user-login")
+ "?"
+ urlencode({"redirect_to": request.path})
)
else:
return view_func(request, *args, **kwargs)

Expand Down
99 changes: 63 additions & 36 deletions ctfpad/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
from django.core.exceptions import ValidationError
from django.forms import widgets

from ctfpad.models import Challenge, ChallengeCategory, ChallengeFile, Ctf, Member, Tag, Team
from ctfpad.models import (
Challenge,
ChallengeCategory,
ChallengeFile,
Ctf,
Member,
Tag,
Team,
)


class UserUpdateForm(UserChangeForm):
Expand All @@ -17,7 +25,9 @@ class Meta:
"email",
]

current_password = forms.CharField(label="Current password", widget=forms.PasswordInput, required=True)
current_password = forms.CharField(
label="Current password", widget=forms.PasswordInput, required=True
)


class TeamCreateUpdateForm(forms.ModelForm):
Expand All @@ -38,20 +48,25 @@ class Meta:
class MemberCreateForm(forms.ModelForm):
class Meta:
model = Member
fields = [
"username",
"email",
"password1",
"password2",
"api_key"
]
fields = ["username", "email", "password1", "password2", "api_key"]

username = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Username'}))
email = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Email address'}))
password1 = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Password'}))
password2 = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Repeat the password'}))
api_key = forms.CharField(required=True, label="api_key",
widget=forms.TextInput(attrs={'placeholder': 'Team API key'}))
username = forms.CharField(
required=True, widget=forms.TextInput(attrs={"placeholder": "Username"})
)
email = forms.CharField(
required=True, widget=forms.TextInput(attrs={"placeholder": "Email address"})
)
password1 = forms.CharField(
widget=forms.PasswordInput(attrs={"placeholder": "Password"})
)
password2 = forms.CharField(
widget=forms.PasswordInput(attrs={"placeholder": "Repeat the password"})
)
api_key = forms.CharField(
required=True,
label="api_key",
widget=forms.TextInput(attrs={"placeholder": "Team API key"}),
)


class MemberUpdateForm(forms.ModelForm):
Expand All @@ -73,8 +88,8 @@ class Meta:
has_superpowers = forms.BooleanField(required=False, label="Has Super-Powers?")

def clean(self):
status = self.cleaned_data['status'].strip().lower()
if status == "guest" and not self.cleaned_data['selected_ctf']:
status = self.cleaned_data["status"].strip().lower()
if status == "guest" and not self.cleaned_data["selected_ctf"]:
raise ValidationError("Guests MUST have a selected_ctf")
return super(MemberUpdateForm, self).clean()

Expand Down Expand Up @@ -133,7 +148,7 @@ class Meta:
]

def cleaned_tags(self):
data = [x.lower() for x in self.cleaned_data['tags'].split()]
data = [x.lower() for x in self.cleaned_data["tags"].split()]
return data


Expand All @@ -143,54 +158,66 @@ class ChallengeImportForm(forms.Form):
("CTFd", "CTFd"),
("rCTF", "rCTF"),
)
format = forms.ChoiceField(choices=FORMAT_CHOICES, initial='CTFd')
format = forms.ChoiceField(choices=FORMAT_CHOICES, initial="CTFd")
data = forms.CharField(widget=forms.Textarea)

def clean_data(self):
data = self.cleaned_data['data']
data = self.cleaned_data["data"]

# Choose the cleaning method based on the format field.
if self.cleaned_data['format'] == 'RAW':
if self.cleaned_data["format"] == "RAW":
return self._clean_raw_data(data)
elif self.cleaned_data['format'] == 'CTFd':
elif self.cleaned_data["format"] == "CTFd":
return self._clean_ctfd_data(data)
elif self.cleaned_data['format'] == 'rCTF':
elif self.cleaned_data["format"] == "rCTF":
return self._clean_rctf_data(data)
else:
raise forms.ValidationError('Invalid data format.')
raise forms.ValidationError("Invalid data format.")

@staticmethod
def _clean_raw_data(data):
challenges = []
for line in data.splitlines():
parts = line.split('|')
parts = line.split("|")
if len(parts) != 2:
raise forms.ValidationError('RAW data line does not have exactly two parts.')
challenges.append({
'name': parts[0].strip(),
'category': parts[1].strip(),
})
raise forms.ValidationError(
"RAW data line does not have exactly two parts."
)
challenges.append(
{
"name": parts[0].strip(),
"category": parts[1].strip(),
}
)
return challenges

@staticmethod
def _clean_ctfd_data(data):
try:
json_data = json.loads(data)
if not json_data.get('success') or 'data' not in json_data:
raise ValidationError('Invalid JSON format. Please provide valid CTFd JSON data.')
if not json_data.get("success") or "data" not in json_data:
raise ValidationError(
"Invalid JSON format. Please provide valid CTFd JSON data."
)
except json.JSONDecodeError:
raise ValidationError('Invalid JSON format. Please provide valid CTFd JSON data.')
raise ValidationError(
"Invalid JSON format. Please provide valid CTFd JSON data."
)

return json_data["data"]

@staticmethod
def _clean_rctf_data(data):
try:
json_data = json.loads(data)
if "successful" not in json_data.get('message') or 'data' not in json_data:
raise ValidationError('Invalid JSON format. Please provide valid rCTF JSON data.')
if "successful" not in json_data.get("message") or "data" not in json_data:
raise ValidationError(
"Invalid JSON format. Please provide valid rCTF JSON data."
)
except json.JSONDecodeError:
raise ValidationError('Invalid JSON format. Please provide valid rCTF JSON data.')
raise ValidationError(
"Invalid JSON format. Please provide valid rCTF JSON data."
)

return json_data["data"]

Expand Down
36 changes: 24 additions & 12 deletions ctfpad/helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import os
import pathlib
import smtplib
import time
import uuid

from datetime import datetime
from functools import lru_cache
from time import time
from uuid import uuid4

import django.core.mail
import django.utils.crypto
import exrex
import magic
import requests

import exrex
import magic
Expand Down Expand Up @@ -84,7 +91,7 @@ def create_new_note() -> str:
Returns:
str: the string ID of the new note
"""
return f"/{uuid4()}"
return f"/{uuid.uuid4()}"


def check_note_id(id: str) -> bool:
Expand Down Expand Up @@ -170,8 +177,10 @@ def ctftime_fetch_ctfs(limit=100) -> list:
Returns:
list: JSON output from CTFTime
"""
start = time.time() - (3600 * 24 * 60)
end = time.time() + (3600 * 24 * 7 * 26)
res = requests.get(
f"{CTFTIME_API_EVENTS_URL}?limit={limit}&start={time() - (3600 * 24 * 60):.0f}&finish={time() + (3600 * 24 * 7 * 26):.0f}",
f"{CTFTIME_API_EVENTS_URL}?limit={limit}&start={start:.0f}&finish={end:.0f}",
headers={"user-agent": CTFTIME_USER_AGENT},
)
if res.status_code != requests.codes.ok:
Expand Down Expand Up @@ -233,8 +242,8 @@ def ctftime_get_ctf_logo_url(ctftime_id: int) -> str:
return default_logo


def send_mail(recipients: list, subject: str, body: str) -> bool:
"""[summary]
def send_mail(recipients: list[str], subject: str, body: str) -> bool:
"""Wrapper to easily send an email
Args:
recipients (list): [description]
Expand All @@ -246,26 +255,29 @@ def send_mail(recipients: list, subject: str, body: str) -> bool:
"""
if EMAIL_HOST and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD:
try:
send_mail(subject, body, EMAIL_HOST_USER, recipients, fail_silently=False)
django.core.mail.send_mail(
subject, body, EMAIL_HOST_USER, recipients, fail_silently=False
)
return True
except smtplib.SMTPException:
pass
return False


def get_random_string_64() -> str:
return get_random_string(64)
return django.utils.crypto.get_random_string(64)


def get_random_string_128() -> str:
return get_random_string(128)
return django.utils.crypto.get_random_string(128)


def discord_send_message(js: dict) -> bool:
"""Send a notification on a Discord channel
Args:
js (dict): The JSON data to pass to the webhook. See https://discord.com/developers/docs/resources/channel for details
js (dict): The JSON data to pass to the webhook. See https://discord.com/developers/docs/resources/channel for
details
Raises:
Exception: [description]
Expand Down Expand Up @@ -312,12 +324,12 @@ def generate_github_page_header(**kwargs) -> str:
return content


def export_challenge_note(member, note_id: uuid4) -> str:
def export_challenge_note(member, note_id: uuid.UUID) -> str:
"""Export a challenge note. `member` is required for privilege requirements
Args:
member (Member): [description]
note_id (uuid.uuid4): [description]
note_id (uuid.UUID): [description]
Returns:
str: The body of the note if successful; an empty string otherwise
Expand Down
Loading

0 comments on commit a040458

Please sign in to comment.