added models and the flag page

This commit is contained in:
faraphel 2025-01-26 11:26:35 +01:00
parent e1b5e95e0e
commit 8410cc33da
11 changed files with 274 additions and 6 deletions

View file

@ -1,3 +1,17 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. from source.apps.ctf import models
@admin.register(models.Team)
class TeamAdmin(admin.ModelAdmin):
list_display = ("id", "name")
@admin.register(models.Flag)
class FlagAdmin(admin.ModelAdmin):
list_display = ("id", "name", "type")
@admin.register(models.Hint)
class HintAdmin(admin.ModelAdmin):
list_display = ("id", "flag")

View file

@ -3,4 +3,4 @@ from django.apps import AppConfig
class CtfConfig(AppConfig): class CtfConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.ctf' name = 'source.apps.ctf'

9
source/apps/ctf/forms.py Normal file
View file

@ -0,0 +1,9 @@
from django import forms
class TeamForm(forms.Form):
name = forms.CharField(label="Team's name", max_length=32)
class FlagForm(forms.Form):
name = forms.UUIDField(label="Identifier", max_length=32)

View file

@ -1,3 +1,65 @@
import uuid
from django.db import models from django.db import models
# Create your models here.
class Team(models.Model):
"""
Represent a team participating in the event
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=32)
validated_flags = models.ManyToManyField(to="Flag", blank=True, related_name="validated_by_teams")
def __str__(self):
return f"<{self.__class__.__name__}({self.name!r}, {self.id!r})>"
class Flag(models.Model):
"""
Represent a flag that can be validated by the team
"""
class FlagType(models.IntegerChoices):
NORMAL = 0
BONUS = 1
MALUS = 2
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# the flag might depend on other flags
parents = models.ManyToManyField(
"self",
blank=True,
symmetrical=False,
related_name="children",
)
# information about the flags
# TODO(Faraphel): store code as hash ?
code = models.CharField(max_length=32) # the password that must be given to obtain the flag
name = models.CharField(max_length=32) # the name of the flag
description = models.TextField() # the description on how to obtain the flag
# TODO(Faraphel): replace with or add a points amount you gain ?
type = models.IntegerField(choices=FlagType.choices, default=FlagType.NORMAL)
def __str__(self):
return f"<{self.__class__.__name__}({self.name!r}, {self.id!r})>"
class Hint(models.Model):
"""
Represent a hint that can be given to a team
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
importance = models.PositiveIntegerField()
description = models.TextField()
penalty = models.PositiveIntegerField()
flag = models.ForeignKey(to=Flag, on_delete=models.CASCADE, related_name="hints")
def __str__(self):
return f"<{self.__class__.__name__}({self.flag.name!r}, {self.id!r})>"

View file

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
{% block head %} {% block head %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,105 @@
{% extends "ctf/base/base.html" %}
{% block title %}Flags{% endblock %}
{% block head %}
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script src="https://cytoscape.org/cytoscape.js-popper/cytoscape-popper.js"></script>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<style>
#cy {
width: 100vw;
height: 100vh;
}
</style>
{% endblock %}
{% block body %}
<div id="cy"></div>
<script>
function tippyFactory(reference, content) {
// create a basic element for the tips object
const element = document.createElement('div');
// build the tip inside
return tippy(element, {
getReferenceClientRect: reference.getBoundingClientRect,
trigger: "manual",
content: content,
arrow: true,
placement: "bottom",
hideOnClick: false,
sticky: "reference",
interactive: true,
appendTo: cy.container()
});
}
// use the factory when creating tips
cytoscape.use(cytoscapePopper(tippyFactory));
// create a graph based on our data
const cy = cytoscape({
container: document.getElementById('cy'),
// content
elements: [
// nodes
{% for flag in flags %}
{data: {
id: "{{ flag.id }}",
name: "{{ flag.name }}",
description: "{{ flag.description }}",
teams: [
{% for team in flag.validated_by_teams.all %}
"{{ team.name }}",
{% endfor %}
],
}},
{% endfor %}
// links
{% for flag in flags %}
{% for child_flag in flag.children.all %}
{data: {source: "{{ child_flag.id }}", target: "{{ flag.id }}"}},
{% endfor %}
{% endfor %}
],
// style
style: [
{selector: 'node', style: {'label': 'data(name)'}},
{selector: 'edge', style: {'target-arrow-shape': 'triangle'}},
],
// layout
layout: {
name: 'breadthfirst',
directed: true
}
});
// show the description of the nodes on hover
cy.ready(() => {
cy.nodes().forEach(node => {
// build the tip
const tippy = node.popper({
content: () => {
const div = document.createElement('div');
div.innerHTML = node.data("description") + "<br/>---<br/>" + node.data("teams");
return div;
},
trigger: 'manual',
interactive: true,
appendTo: cy.container()
});
// events
node.on('mouseover', () => tippy.show());
node.on('mouseout', () => tippy.hide());
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "ctf/base/base.html" %}
{% block title %}Flag - Submit{% endblock %}
{% block body %}
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "ctf/base/base.html" %}
{% block title %}Team - Create{% endblock %}
{% block body %}
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends "ctf/base/base.html" %}
{% block title %}Teams{% endblock %}
{% block body %}
<ul>
{% for team in teams %}
<li>{{ team.name }}</li>
{% endfor %}
</ul>
<a href="{% url "team_create" %}">Create</a>
{% endblock %}

View file

@ -3,5 +3,9 @@ from django.urls import path
from source.apps.ctf import views from source.apps.ctf import views
urlpatterns = [ urlpatterns = [
path("", views.homepage, name='homepage'), path("", views.view_homepage, name='homepage'),
path("teams", views.view_teams, name='teams'),
path("teams/create", views.view_team_create, name='team_create'),
path("flags", views.view_flags, name='flags'),
path("flags/submit", views.view_flag_submit, name='flag_submit'),
] ]

View file

@ -1,5 +1,43 @@
from django.shortcuts import render from django.shortcuts import render, redirect
from source.apps.ctf import forms, models
# Create your views here. # Create your views here.
async def homepage(request): async def view_homepage(request):
return render(request, "ctf/homepage.html") return render(request, "ctf/homepage.html")
async def view_teams(request):
teams = [team async for team in models.Team.objects.all()]
return render(request, "ctf/team_list.html", context={"teams": teams})
async def view_team_create(request):
if request.method == "POST":
form = forms.TeamForm(request.POST)
if form.is_valid():
# TODO(Faraphel): additional password to prevent unwanted team creation ?
await models.Team.objects.acreate(name=form.cleaned_data["name"])
return redirect("/")
else:
form = forms.TeamForm()
return render(request, "ctf/team_create.html", context={"form": form})
def view_flags(request):
flags = models.Flag.objects.all()
return render(request, "ctf/flag_list.html", context={"flags": flags})
async def view_flag_submit(request):
if request.method == "POST":
form = forms.FlagForm(request.POST)
if form.is_valid():
return redirect("/")
else:
form = forms.FlagForm()
return render(request, "ctf/flag_submit.html", context={"form": form})