Skip to content

Commit f55ad31

Browse files
committed
implemented caching and other fixes
1 parent 4c47df9 commit f55ad31

26 files changed

+426
-186
lines changed

app.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"addons": [
66
{
77
"plan": "heroku-postgresql"
8+
},
9+
{
10+
"plan": "heroku-redis"
811
}
912
],
1013
"buildpacks": [
@@ -21,6 +24,9 @@
2124
"description": "Administrator password",
2225
"generator": "secret"
2326
},
27+
"ADMIN_EMAIL": {
28+
"description": "Administrator email"
29+
},
2430
"MAIL_USER": {
2531
"description": "Username for mail service",
2632
"required": false

src/FlaskRTBCTF/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
from flask import Flask
44

55
from FlaskRTBCTF.config import Config
6-
from FlaskRTBCTF.admin.views import BaseModelView, UserAdminView, MachineAdminView
6+
from FlaskRTBCTF.admin.views import (
7+
BaseModelView,
8+
UserAdminView,
9+
MachineAdminView,
10+
NotificationAdminView,
11+
)
712
from FlaskRTBCTF.utils import (
813
db,
914
bcrypt,
15+
cache,
1016
login_manager,
1117
admin_manager,
1218
mail,
@@ -30,6 +36,7 @@ def create_app(config_class=Config):
3036

3137
db.init_app(app)
3238
bcrypt.init_app(app)
39+
cache.init_app(app)
3340
login_manager.init_app(app)
3441
admin_manager.init_app(app)
3542
mail.init_app(app)
@@ -39,7 +46,7 @@ def create_app(config_class=Config):
3946
# Add model views for admin control
4047
admin_manager.add_view(UserAdminView(User, db.session))
4148
admin_manager.add_view(MachineAdminView(Machine, db.session))
42-
admin_manager.add_view(BaseModelView(Notification, db.session))
49+
admin_manager.add_view(NotificationAdminView(Notification, db.session))
4350
admin_manager.add_view(BaseModelView(Logs, db.session))
4451

4552
for _bp in _blueprints:

src/FlaskRTBCTF/admin/views.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
""" Admin Model Views. """
22

3-
from flask import abort, redirect, flash
3+
from flask import abort, redirect, flash, url_for, request
44
from flask_login import current_user
55
from flask_admin import expose
66
from flask_admin.form import SecureForm
@@ -32,6 +32,7 @@ def _handle_view(self, name, **kwargs):
3232

3333
class UserAdminView(BaseModelView):
3434
column_exclude_list = ("password",)
35+
form_exclude_list = ("password",)
3536
column_searchable_list = ("username", "email")
3637

3738
@expose("/new/")
@@ -42,6 +43,17 @@ def create_view(self):
4243

4344
class MachineAdminView(BaseModelView):
4445
column_searchable_list = ("name", "ip")
45-
form_choices = {
46-
"hardness": [("easy", "Easy"), ("medium", "Medium"), ("hard", "Hard")]
47-
}
46+
47+
@expose("/new/")
48+
def create_view(self):
49+
return redirect(url_for("ctf.new_machine"))
50+
51+
@expose("/edit/")
52+
def edit_view(self):
53+
id = int(request.args["id"])
54+
return redirect(url_for("ctf.edit_machine", id=id))
55+
56+
57+
class NotificationAdminView(BaseModelView):
58+
column_searchable_list = ("title",)
59+
form_excluded_columns = ("timestamp",)

src/FlaskRTBCTF/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88

99
class Config:
10+
DEBUG = False # Turn DEBUG OFF before deployment
1011
SECRET_KEY = handle_secret_key()
1112
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///site.db"
1213
# For local use, one can simply use SQLlite with: 'sqlite:///site.db'
1314
# For deployment on Heroku use: `os.environ.get('DATABASE_URL')`
1415
# in all other cases: `os.environ.get('SQLALCHEMY_DATABASE_URI')`
15-
FLASK_ADMIN_SWATCH = ["journal", "paper", "yeti", "cosmo"][2]
1616
SQLALCHEMY_TRACK_MODIFICATIONS = False
17-
DEBUG = False # Turn DEBUG OFF before deployment
17+
FLASK_ADMIN_SWATCH = ("journal", "paper", "yeti", "cosmo")[3]
1818
# TEMPLATES_AUTO_RELOAD = True
1919
MAIL_SERVER = "smtp.googlemail.com"
2020
MAIL_PORT = 587

src/FlaskRTBCTF/ctf/forms.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
11
from flask_wtf import FlaskForm
2-
from wtforms import StringField, SubmitField, HiddenField
3-
from wtforms.validators import DataRequired, Length, ValidationError
2+
from wtforms import StringField, SubmitField, HiddenField, RadioField
3+
from wtforms.validators import DataRequired, Length, ValidationError, IPAddress
4+
from wtforms.fields.html5 import IntegerField
45
from .models import Machine
56

67

8+
class MachineForm(FlaskForm):
9+
name = StringField("Name", validators=[DataRequired(), Length(min=4, max=32)])
10+
os = RadioField(
11+
"Operating System of machine",
12+
validators=[DataRequired()],
13+
choices=(("linux", "Linux"), ("windows", "Windows"), ("android", "Android")),
14+
)
15+
user_hash = StringField(
16+
"User Hash", validators=[DataRequired(), Length(min=32, max=32)]
17+
)
18+
root_hash = StringField(
19+
"Root Hash", validators=[DataRequired(), Length(min=32, max=32)]
20+
)
21+
user_points = IntegerField("Points for User Hash", validators=[DataRequired()])
22+
root_points = IntegerField("Points for Root Hash", validators=[DataRequired()])
23+
ip = StringField(
24+
"IPv4 address of machine", validators=[DataRequired(), IPAddress()]
25+
)
26+
hardness = RadioField(
27+
"Difficuly Level",
28+
validators=[DataRequired()],
29+
choices=(
30+
("easy", "Easy"),
31+
("medium", "Medium"),
32+
("hard", "Hard"),
33+
("insane", "Insane"),
34+
),
35+
)
36+
37+
submit = SubmitField("Submit")
38+
39+
740
class UserHashForm(FlaskForm):
841
machine_id = HiddenField("Machine ID", validators=[DataRequired()])
942
user_hash = StringField(
@@ -30,7 +63,7 @@ def validate_root_hash(self, root_hash):
3063
box = Machine.query.get(int(self.machine_id.data))
3164
if not box:
3265
raise ValidationError("No machine with that ID exists")
33-
elif box.user_hash == str(root_hash.data):
66+
elif box.root_hash == str(root_hash.data):
3467
pass
3568
else:
3669
raise ValidationError("Incorrect Root Hash.")

src/FlaskRTBCTF/ctf/models.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from FlaskRTBCTF.utils import db
1+
from FlaskRTBCTF.utils import db, cache
22

33
# Machine Table
44

@@ -11,6 +11,12 @@ class Machine(db.Model):
1111
root_hash = db.Column(db.String(32), nullable=False)
1212
user_points = db.Column(db.Integer, default=0)
1313
root_points = db.Column(db.Integer, default=0)
14-
os = db.Column(db.String(16), nullable=False)
15-
ip = db.Column(db.String(45), nullable=False)
16-
hardness = db.Column(db.String(16), nullable=False, default="Easy")
14+
os = db.Column(db.String, nullable=False, default="linux")
15+
ip = db.Column(db.String(64), nullable=False)
16+
hardness = db.Column(db.String, nullable=False, default="Easy")
17+
18+
@staticmethod
19+
@cache.cached(timeout=3600, key_prefix="machines")
20+
def get_all():
21+
_machines = Machine.query.all()
22+
return _machines

src/FlaskRTBCTF/ctf/routes.py

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,28 @@
66
from flask import Blueprint, render_template, flash, request, redirect, url_for
77
from flask_login import current_user, login_required
88

9-
from FlaskRTBCTF import db
109
from FlaskRTBCTF.users.models import User, Logs
11-
from FlaskRTBCTF.utils import is_past_running_time
10+
from FlaskRTBCTF.utils import db, cache, is_past_running_time, admin_only
1211
from .models import Machine
13-
from .forms import UserHashForm, RootHashForm
12+
from .forms import UserHashForm, RootHashForm, MachineForm
1413

1514

1615
ctf = Blueprint("ctf", __name__)
1716

1817

19-
# context processor
20-
21-
22-
@ctf.context_processor
23-
def inject_context():
24-
boxes = Machine.query.all()
25-
past_running_time = is_past_running_time()
26-
27-
return dict(boxes=boxes, past_running_time=past_running_time)
28-
29-
3018
# Scoreboard
3119

3220

3321
@ctf.route("/scoreboard")
34-
@login_required
22+
@cache.cached(timeout=120, key_prefix="scoreboard")
3523
def scoreboard():
36-
users_score = User.query.order_by(User.points.desc()).all()
37-
userNameScoreList = []
38-
for u in users_score:
39-
userNameScoreList.append({"username": u.username, "score": u.points})
24+
users_scores = (
25+
User.query.with_entities(User.username, User.points)
26+
.order_by(User.points.desc())
27+
.all()
28+
)
4029

41-
return render_template("scoreboard.html", scores=userNameScoreList)
30+
return render_template("scoreboard.html", scores=users_scores)
4231

4332

4433
# Machines Info
@@ -50,7 +39,11 @@ def machines():
5039
userHashForm = UserHashForm()
5140
rootHashForm = RootHashForm()
5241

42+
boxes = Machine.get_all()
43+
past_running_time = is_past_running_time()
44+
5345
if request.method == "GET":
46+
5447
log = Logs.query.get(current_user.id)
5548

5649
# check if it is the first visit to machine page for user
@@ -60,50 +53,106 @@ def machines():
6053
db.session.commit()
6154

6255
else:
63-
if is_past_running_time():
56+
if past_running_time:
6457
flash("Sorry! CTF has ended.", "danger")
6558
return redirect(url_for("ctf.machines"))
6659

6760
"""
6861
Todo: Get Object from UserMachine Model, dummy object given below
6962
"""
70-
user_machine: object = {
71-
"machine_id": 1,
72-
"user_id": 1,
73-
"owned_user": False,
74-
"owned_root": False,
75-
}
76-
77-
if user_machine.owned_user:
78-
flash("You already own User.", "success")
79-
return redirect(url_for("ctf.machines"))
80-
81-
elif user_machine.owned_root:
82-
flash("You already own System.", "success")
83-
return redirect(url_for("ctf.machines"))
84-
85-
elif userHashForm.submit_user_hash.data and userHashForm.validate_on_submit():
63+
# user_machine: object = {
64+
# "machine_id": 1,
65+
# "user_id": 1,
66+
# "owned_user": False,
67+
# "owned_root": False,
68+
# }
69+
70+
# if user_machine.owned_user:
71+
# flash("You already own User.", "success")
72+
# return redirect(url_for("ctf.machines"))
73+
74+
# elif user_machine.owned_root:
75+
# flash("You already own System.", "success")
76+
# return redirect(url_for("ctf.machines"))
77+
78+
if userHashForm.submit_user_hash.data and userHashForm.validate_on_submit():
8679
box = Machine.query.get(int(userHashForm.machine_id.data))
87-
user_machine.owned_user = True
80+
# user_machine.owned_user = True
8881
current_user.points += box.user_points
8982
log = Logs.query.get(current_user.id)
9083
log.userSubmissionIP = request.access_route[0]
9184
log.userSubmissionTime = datetime.utcnow()
9285
log.userOwnTime = str(log.userSubmissionTime - log.machineVisitTime)
9386
db.session.commit()
87+
cache.delete(key="scoreboard")
9488
flash("Congrats! correct user hash.", "success")
9589

9690
elif rootHashForm.submit_root_hash.data and rootHashForm.validate_on_submit():
9791
box = Machine.query.get(int(rootHashForm.machine_id.data))
98-
user_machine.owned_root = True
92+
# user_machine.owned_root = True
9993
current_user.points += box.root_points
10094
log = Logs.query.get(current_user.id)
10195
log.rootSubmissionIP = request.access_route[0]
10296
log.rootSubmissionTime = datetime.utcnow()
10397
log.rootOwnTime = str(log.rootSubmissionTime - log.machineVisitTime)
10498
db.session.commit()
99+
cache.delete(key="scoreboard")
105100
flash("Congrats! correct root hash.", "success")
106101

102+
else:
103+
errors = userHashForm.user_hash.errors or rootHashForm.root_hash.errors
104+
for e in errors:
105+
flash(e, "danger")
106+
107+
return redirect(url_for("ctf.machines"))
108+
107109
return render_template(
108-
"machine.html", userHashForm=userHashForm, rootHashForm=rootHashForm,
110+
"machines.html",
111+
boxes=boxes,
112+
past_running_time=past_running_time,
113+
userHashForm=userHashForm,
114+
rootHashForm=rootHashForm,
109115
)
116+
117+
118+
@ctf.route("/machines/new", methods=["GET", "POST"])
119+
@admin_only
120+
def new_machine():
121+
form = MachineForm(obj=Machine.query.get(1))
122+
if request.method == "GET":
123+
return render_template(
124+
"new_machine.html", form_title="Add New Machine", form=form
125+
)
126+
else:
127+
if form.validate_on_submit():
128+
new_machine = Machine()
129+
form.populate_obj(new_machine)
130+
db.session.add(new_machine)
131+
db.session.commit()
132+
cache.delete(key="machines")
133+
flash(f"{form.name.data} has been added.", "success")
134+
return redirect(url_for("ctf.machines"))
135+
else:
136+
flash(form.errors, "danger")
137+
return redirect(request.url)
138+
139+
140+
@ctf.route("/machines/edit/<int:id>", methods=["GET", "POST"])
141+
@admin_only
142+
def edit_machine(id):
143+
machine = Machine.query.get_or_404(id)
144+
form = MachineForm(obj=machine)
145+
if request.method == "GET":
146+
return render_template(
147+
"new_machine.html", form_title=f"Editing machine #{id}", form=form
148+
)
149+
else:
150+
if form.validate_on_submit():
151+
form.populate_obj(machine)
152+
db.session.commit()
153+
cache.delete(key="machines")
154+
flash(f"{form.name.data} has been edited.", "success")
155+
return redirect(url_for("ctf.machines"))
156+
else:
157+
flash(form.errors, "danger")
158+
return redirect(request.url)

0 commit comments

Comments
 (0)