diff --git a/.lgtm.yml b/.lgtm.yml
index 607830e..91c7209 100644
--- a/.lgtm.yml
+++ b/.lgtm.yml
@@ -1,6 +1,7 @@
queries:
- exclude: py/similar-function
- exclude: py/empty-except
+ - exclude: py/call-to-non-callable
- include: py/undefined-placeholder-variable
- include: py/uninitialized-local-variable
- include: py/request-without-cert-validation
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 31f475d..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1,7 +0,0 @@
-[FORMAT]
-indent-string=\t
-
-[BASIC]
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=organization,RunningTime,box,userHash,rootHash,userScore,rootScore,admin,db,bcrypt
diff --git a/.travis.yml b/.travis.yml
index 0a1f08b..5fbd182 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,13 @@
---
env: SKIP_INTERPRETER=true
-branches:
- only:
- - master
+#branches:
+# only:
+# - master
language: python
python:
- - "3.7.3"
- - "3.8.1"
+ - "3.8.2"
env:
global:
@@ -20,9 +19,7 @@ before_install:
install:
- "pip install -r src/requirements.txt"
- - pip install --no-cache-dir pytest-flake8
- "python src/create_db.py"
-
script:
- - pytest --lint-only --flake8
+ - pytest --flake8
diff --git a/README.md b/README.md
index 65a9c77..76f1fd9 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ The main purpose of this project is to serve as a scoring engine and CTF manager
### Requirements
-* `Python 3.7.3` or atleast `> 3.6`.
+* Tested on `Python 3.8.2`
* Python Packages: [`src/requirements.txt`](src/requirements.txt).
* OS Packages: PostgreSQL version 11 or greater, `libpq-dev`, `python3-dev` packages. Please refer [here](https://tutorials.technology/solved_errors/9-Error-pg_config-executable-not-found.html).
@@ -138,34 +138,31 @@ Bonus: You can manage the database CRUD operations from admin views GUI as well
-Please see: [Issues](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues) and the below To-do list.
+Keeping to a consistent code style throughout the project makes it easier to contribute and collaborate. Please stick to the guidelines in PEP8 and the Google Style Guide unless thereβs a very good reason not to.
+Please see: [Issues](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues) and the following To-do list.
> Note: All PRs within the GSSoC'20 period will be merged in the `gssoc20-dev` branch.
-#### π¨ Project Owner
+##### π¨ Project Owner
- Eshaan Bansal ([github](https://github.com/eshaan7),[linkedin](https://www.linkedin.com/in/eshaan7/))
-#### π¬ Mentors
+##### π¬ Mentors
- Sombuddha Chakravarty ([github](https://github.com/sammy1997),[linkedin](https://www.linkedin.com/in/sombuddha-chakravarty-9482b5131/))
Feel free to ask your queries!! π
-#### Slack Channel
+##### Slack Channel
- [#proj_root-the-box-ctf-framework](https://app.slack.com/client/TRN1H1V43/CUC71PDD2)
-
## To-do
-- [ ] Freeze Scoreboard automatically past running time specified (Issue: [#3](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/3))
- [ ] Ideas for additional logging techniques to prevent flag sharing, cheating and such. (Issue: [#7](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/7))
- [ ] Support for *n* number of boxes (accordions? seperate route?). (Issue: [#17](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/17))
- [ ] Rating system: Average Box rating - input, calculate, output. (Issue: [#14](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/14))
-- [ ] Adding a `Deploy to Heroku` button. (Issue: [#15](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/15))
- [ ] Dark theme for `admin control` panel. (Issue: [#16](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/16))
-- [ ] Adding CI, Linting, Formatting specs. (Issue: [#18](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/18))
- [ ] Testing Password reset functionality, the mail-server setup, etc.
- [ ] More info on `home.html`
- [ ] Support for more hashes per box (not a priority)
@@ -173,6 +170,9 @@ Feel free to ask your queries!! π
+- [x] Freeze Scoreboard automatically past running time specified (Issue: [#3](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/3))
+- [x] Adding a `Deploy to Heroku` button. (Issue: [#15](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/15))
+- [x] Adding CI, Linting, Formatting specs. (Issue: [#18](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/18))
- [x] db relationship between User and Score Tables (priority | issue: #5)
- [x] isAdmin column in User table and Admin views (priority)
- [x] Notifications
diff --git a/src/FlaskRTBCTF/__init__.py b/src/FlaskRTBCTF/__init__.py
index 684efdd..23e773a 100644
--- a/src/FlaskRTBCTF/__init__.py
+++ b/src/FlaskRTBCTF/__init__.py
@@ -38,8 +38,9 @@ def create_app(config_class=Config):
mail.init_app(app)
from flask_sslify import SSLify
- if 'DYNO' in os.environ: # only trigger SSLify if the app is running on Heroku
- _sslify = SSLify(app)
+ # only trigger SSLify if the app is running on Heroku
+ if 'DYNO' in os.environ:
+ _ = SSLify(app)
from FlaskRTBCTF.users.routes import users
from FlaskRTBCTF.ctf.routes import ctf
diff --git a/src/FlaskRTBCTF/admin/views.py b/src/FlaskRTBCTF/admin/views.py
index 14a014b..2d72f9c 100644
--- a/src/FlaskRTBCTF/admin/views.py
+++ b/src/FlaskRTBCTF/admin/views.py
@@ -4,14 +4,15 @@
from flask_login import current_user
from flask_admin.contrib.sqla import ModelView
+
class MyModelView(ModelView):
- column_exclude_list = ( 'password' )
+ column_exclude_list = ('password',)
def is_accessible(self):
if not current_user.is_authenticated or not current_user.isAdmin:
- # permission denied
- abort(403)
+ # permission denied
+ abort(403)
if current_user.isAdmin:
return True
return False
@@ -23,5 +24,5 @@ def _handle_view(self, name, **kwargs):
"""
if not self.is_accessible():
if current_user.is_authenticated:
- #permission denied
+ # permission denied
abort(403)
diff --git a/src/FlaskRTBCTF/config.py b/src/FlaskRTBCTF/config.py
index a6dc705..70a78e3 100644
--- a/src/FlaskRTBCTF/config.py
+++ b/src/FlaskRTBCTF/config.py
@@ -3,54 +3,58 @@
import pytz
-''' Flask related Configurations. Note: DO NOT FORGET TO CHANGE 'SECRET_KEY' ! '''
+''' Flask related Configurations
+ Note: DO NOT FORGET TO CHANGE 'SECRET_KEY' ! '''
class Config:
- SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
- SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///site.db'
- # For local use, one can simply use SQLlite with: 'sqlite:///site.db'
- # For deployment on Heroku use: `os.environ.get('DATABASE_URL')`
+ SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
+ SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') \
+ or 'sqlite:///site.db'
+ # For local use, one can simply use SQLlite with: 'sqlite:///site.db'
+ # For deployment on Heroku use: `os.environ.get('DATABASE_URL')`
# in all other cases: `os.environ.get('SQLALCHEMY_DATABASE_URI')`
- SQLALCHEMY_TRACK_MODIFICATIONS = False
- DEBUG = False # Turn DEBUG OFF before deployment
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
+ DEBUG = False # Turn DEBUG OFF before deployment
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('EMAIL_USER')
MAIL_PASSWORD = os.environ.get('EMAIL_PASS')
+
''' CTF related Configuration '''
+
# Add some information about organization and specify CTF name
organization = {
"ctfname": "RootTheBox CTF",
"name": "Abs0lut3Pwn4g3",
- "website": {
+ "website": {
"url": "https://Abs0lut3Pwn4g3.github.io/",
"name": "Official Abs0lut3Pwn4g3 Website"
- },
- "website_2": {
+ },
+ "website_2": {
"url": "https://twitter.com/abs0lut3pwn4g3",
"name": "Twitter"
- },
- "website_3": {
+ },
+ "website_3": {
"url": "https://github.com/abs0lut3pwn4g3",
"name": "Github"
}
-}
+}
# Specify CTFs Running Time
-RunningTime = {
- "from": datetime(2019,7,7,15,00,00,0, pytz.utc),
- "to": datetime(2030,7,8,0,00,00,0, pytz.utc),
+RunningTime = {
+ "from": datetime(2019, 7, 7, 15, 00, 00, 0, pytz.utc),
+ "to": datetime(2030, 7, 8, 0, 00, 00, 0, pytz.utc),
"TimeZone": "UTC"
-} # We do not recommend changing the Timezone.
+} # We do not recommend changing the Timezone.
# Logging: Set to 'True' to enable Logging in Admin Views.
-LOGGING = True # We recommend to leave it on. It is more than just errors ;)
+LOGGING = True # We recommend to leave it on. It is more than just errors ;)
# NOTE: CHANGE DEFAULT ADMIN CREDENTIALS in create_db.py !!!
diff --git a/src/FlaskRTBCTF/ctf/forms.py b/src/FlaskRTBCTF/ctf/forms.py
index e931dc5..704b4ab 100644
--- a/src/FlaskRTBCTF/ctf/forms.py
+++ b/src/FlaskRTBCTF/ctf/forms.py
@@ -2,10 +2,22 @@
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length
+
class UserHashForm(FlaskForm):
- userHash = StringField('User hash', validators=[DataRequired(), Length(min=32, max=32)])
+ userHash = StringField('User hash',
+ validators=[
+ DataRequired(),
+ Length(min=32, max=32)
+ ]
+ )
submit = SubmitField('Submit')
+
class RootHashForm(FlaskForm):
- rootHash = StringField('Root hash', validators=[DataRequired(), Length(min=32, max=32)])
- submit = SubmitField('Submit')
\ No newline at end of file
+ rootHash = StringField('Root hash',
+ validators=[
+ DataRequired(),
+ Length(min=32, max=32)
+ ]
+ )
+ submit = SubmitField('Submit')
diff --git a/src/FlaskRTBCTF/ctf/routes.py b/src/FlaskRTBCTF/ctf/routes.py
index 6d2fbf3..96a66f4 100644
--- a/src/FlaskRTBCTF/ctf/routes.py
+++ b/src/FlaskRTBCTF/ctf/routes.py
@@ -9,9 +9,10 @@
from FlaskRTBCTF import db
from FlaskRTBCTF.config import organization, LOGGING, RunningTime
from FlaskRTBCTF.models import User, Score, Machine
+from FlaskRTBCTF.ctf.forms import UserHashForm, RootHashForm
+
if LOGGING:
from FlaskRTBCTF.models import Logs
-from FlaskRTBCTF.ctf.forms import UserHashForm, RootHashForm
ctf = Blueprint('ctf', __name__)
@@ -27,11 +28,12 @@ def scoreboard():
userNameScoreList = []
for score in scores:
userNameScoreList.append({
- 'username': User.query.get(score.user_id).username,
- 'score' :score.points
+ 'username': User.query.get(score.user_id).username,
+ 'score': score.points
})
- return render_template('scoreboard.html', scores=userNameScoreList, organization=organization)
+ return render_template('scoreboard.html', scores=userNameScoreList,
+ organization=organization)
''' Machine Info '''
@@ -40,19 +42,21 @@ def scoreboard():
@ctf.route("/machine")
@login_required
def machine():
- box = Machine.query.filter(Machine.ip=="127.0.0.1").first()
+ box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
if LOGGING:
log = Logs.query.get(current_user.id)
if log.visitedMachine is False:
log.visitedMachine = True
- log.machineVisitTime = datetime.now(pytz.utc)
+ log.machineVisitTime = datetime.utcnow()
db.session.commit()
userHashForm = UserHashForm()
rootHashForm = RootHashForm()
end_date_time = RunningTime["to"]
current_date_time = datetime.now(pytz.utc)
return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, organization=organization, box=box, current=current_date_time, end=end_date_time)
+ rootHashForm=rootHashForm,
+ organization=organization, box=box,
+ current=current_date_time, end=end_date_time)
''' Hash Submission Management '''
@@ -61,12 +65,12 @@ def machine():
@ctf.route("/validateRootHash", methods=['POST'])
@login_required
def validateRootHash():
- box = Machine.query.filter(Machine.ip=="127.0.0.1").first()
+ box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
userHashForm = UserHashForm()
rootHashForm = RootHashForm()
end_date_time = RunningTime["to"]
current_date_time = datetime.now(pytz.utc)
- if rootHashForm.validate_on_submit():
+ if rootHashForm.validate_on_submit():
if current_date_time > end_date_time:
flash("Sorry! Contest has ended", "danger")
elif rootHashForm.rootHash.data == box.root_hash:
@@ -80,28 +84,34 @@ def validateRootHash():
if LOGGING:
log = Logs.query.get(current_user.id)
log.rootSubmissionIP = request.access_route[0]
- log.rootSubmissionTime = datetime.now(pytz.utc)
- log.rootOwnTime = str(log.rootSubmissionTime - log.machineVisitTime)
+ log.rootSubmissionTime = datetime.utcnow()
+ log.rootOwnTime = str(
+ log.rootSubmissionTime - log.machineVisitTime
+ )
db.session.commit()
flash("Congrats! correct system hash.", "success")
else:
flash("Sorry! Wrong system hash", "danger")
return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, organization=organization, box=box, current=current_date_time, end=end_date_time)
+ rootHashForm=rootHashForm, box=box,
+ organization=organization,
+ current=current_date_time, end=end_date_time)
else:
return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, organization=organization, box=box, current=current_date_time, end=end_date_time)
+ rootHashForm=rootHashForm, box=box,
+ organization=organization,
+ current=current_date_time, end=end_date_time)
@ctf.route("/validateUserHash", methods=['POST'])
@login_required
def validateUserHash():
- box = Machine.query.filter(Machine.ip=="127.0.0.1").first()
+ box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
userHashForm = UserHashForm()
rootHashForm = RootHashForm()
end_date_time = RunningTime["to"]
- current_date_time =datetime.now(pytz.utc)
- if userHashForm.validate_on_submit():
+ current_date_time = datetime.now(pytz.utc)
+ if userHashForm.validate_on_submit():
if current_date_time > end_date_time:
flash("Sorry! Contest has ended", "danger")
elif userHashForm.userHash.data == box.user_hash:
@@ -115,16 +125,20 @@ def validateUserHash():
if LOGGING:
log = Logs.query.get(current_user.id)
log.userSubmissionIP = request.access_route[0]
- log.userSubmissionTime = datetime.now(pytz.utc)
- log.userOwnTime = str(log.userSubmissionTime - log.machineVisitTime)
+ log.userSubmissionTime = datetime.utcnow()
+ log.userOwnTime = str(
+ log.userSubmissionTime - log.machineVisitTime
+ )
db.session.commit()
flash("Congrats! correct user hash.", "success")
else:
flash("Sorry! Wrong user hash", "danger")
return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, organization=organization, box=box, current=current_date_time, end=end_date_time)
+ rootHashForm=rootHashForm,
+ organization=organization, box=box,
+ current=current_date_time, end=end_date_time)
else:
return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, organization=organization, box=box, current=current_date_time, end=end_date_time)
-
-
+ rootHashForm=rootHashForm,
+ organization=organization, box=box,
+ current=current_date_time, end=end_date_time)
diff --git a/src/FlaskRTBCTF/main/routes.py b/src/FlaskRTBCTF/main/routes.py
index 11a78e7..ebf7811 100644
--- a/src/FlaskRTBCTF/main/routes.py
+++ b/src/FlaskRTBCTF/main/routes.py
@@ -6,12 +6,16 @@
''' Index page '''
+
@main.route("/")
@main.route("/home")
def home():
- return render_template('home.html', organization=organization, RunningTime=RunningTime)
+ return render_template('home.html', organization=organization,
+ RunningTime=RunningTime)
+
@main.route("/notifications")
def notifications():
notifs = Notification.query.order_by(Notification.timestamp.desc()).all()
- return render_template('notifications.html', organization=organization, title="Notifications", notifs=notifs)
\ No newline at end of file
+ return render_template('notifications.html', organization=organization,
+ title="Notifications", notifs=notifs)
diff --git a/src/FlaskRTBCTF/models.py b/src/FlaskRTBCTF/models.py
index 250f1b8..dea8ed6 100644
--- a/src/FlaskRTBCTF/models.py
+++ b/src/FlaskRTBCTF/models.py
@@ -1,18 +1,23 @@
''' Models '''
+
+from datetime import datetime
+from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
+
from flask import current_app
from FlaskRTBCTF.config import LOGGING
from FlaskRTBCTF import db, login_manager
from flask_login import UserMixin
-from datetime import datetime
-from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
+
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
+
''' Machine Table '''
+
class Machine(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), nullable=False)
@@ -26,17 +31,21 @@ class Machine(db.Model):
score = db.relationship('Score', backref='machine', lazy=True)
+
''' User Table '''
+
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
isAdmin = db.Column(db.Boolean, default=False)
- score = db.relationship('Score', backref='user', lazy=True, uselist=False)
+ score = db.relationship('Score', backref='user', lazy=True,
+ uselist=False)
if LOGGING:
- logs = db.relationship('Logs', backref='user', lazy=True, uselist=False)
+ logs = db.relationship('Logs', backref='user', lazy=True,
+ uselist=False)
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
@@ -47,7 +56,7 @@ def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
- except Exception as _e:
+ except Exception:
return None
return User.query.get(user_id)
@@ -57,21 +66,24 @@ def __repr__(self):
''' Score Table '''
+
class Score(db.Model):
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
- nullable=False, primary_key=True)
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False,
+ primary_key=True)
userHash = db.Column(db.Boolean, default=False)
rootHash = db.Column(db.Boolean, default=False)
points = db.Column(db.Integer)
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
machine_id = db.Column(db.Integer, db.ForeignKey('machine.id'),
- nullable=False)
+ nullable=False)
+
def __repr__(self):
return f"Score('{self.user_id}', '{self.points}')"
''' Notifications Table '''
+
class Notification(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(30), nullable=False)
@@ -84,9 +96,11 @@ def __repr__(self):
''' Logging Table '''
+
if LOGGING:
class Logs(db.Model):
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, primary_key=True)
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
+ nullable=False, primary_key=True)
accountCreationTime = db.Column(db.DateTime, nullable=False)
visitedMachine = db.Column(db.Boolean, default=False)
machineVisitTime = db.Column(db.DateTime, nullable=True)
@@ -98,6 +112,4 @@ class Logs(db.Model):
rootSubmissionIP = db.Column(db.String, nullable=True)
def __repr__(self):
- return f"Logs('{self.user_id}','{self.machineVisitTime}','{self.userSubmissionTime}'," \
- f"'{self.rootSubmissionTime}','{self.userOwnTime}','{self.rootOwnTime}','{self.userSubmissionIP}," \
- f" '{self.rootSubmissionIP}'"
+ return f"Logs('{self.user_id}','{self.visitedMachine}'"
diff --git a/src/FlaskRTBCTF/users/forms.py b/src/FlaskRTBCTF/users/forms.py
index 2c41b43..864d7e5 100644
--- a/src/FlaskRTBCTF/users/forms.py
+++ b/src/FlaskRTBCTF/users/forms.py
@@ -1,8 +1,10 @@
+from FlaskRTBCTF.models import User
+
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
-from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
+from wtforms.validators import DataRequired, Length, Email, \
+ EqualTo, ValidationError
from flask_login import current_user
-from FlaskRTBCTF.models import User
class RegistrationForm(FlaskForm):
@@ -12,18 +14,26 @@ class RegistrationForm(FlaskForm):
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
- validators=[DataRequired(), EqualTo('password')])
+ validators=[
+ DataRequired(),
+ EqualTo('password')
+ ]
+ )
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
- raise ValidationError('That username is taken. Please choose a different one.')
+ raise ValidationError(
+ 'That username is taken. Please choose a different one.'
+ )
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
- raise ValidationError('That email is taken. Please choose a different one.')
+ raise ValidationError(
+ 'That email is taken. Please choose a different one.'
+ )
class LoginForm(FlaskForm):
@@ -45,13 +55,17 @@ def validate_username(self, username):
if username.data != current_user.username:
user = User.query.filter_by(username=username.data).first()
if user:
- raise ValidationError('That username is taken. Please choose a different one.')
+ raise ValidationError(
+ 'That username is taken. Please choose a different one.'
+ )
def validate_email(self, email):
if email.data != current_user.email:
user = User.query.filter_by(email=email.data).first()
if user:
- raise ValidationError('That email is taken. Please choose a different one.')
+ raise ValidationError(
+ 'That email is taken. Please choose a different one.'
+ )
class RequestResetForm(FlaskForm):
@@ -62,11 +76,17 @@ class RequestResetForm(FlaskForm):
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is None:
- raise ValidationError('There is no account with that email. You must register first.')
+ raise ValidationError(
+ 'There is no account with that email. You must register first.'
+ )
class ResetPasswordForm(FlaskForm):
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
- validators=[DataRequired(), EqualTo('password')])
+ validators=[
+ DataRequired(),
+ EqualTo('password')
+ ]
+ )
submit = SubmitField('Reset Password')
diff --git a/src/FlaskRTBCTF/users/routes.py b/src/FlaskRTBCTF/users/routes.py
index 8e587a1..bf26c20 100644
--- a/src/FlaskRTBCTF/users/routes.py
+++ b/src/FlaskRTBCTF/users/routes.py
@@ -1,16 +1,18 @@
from datetime import datetime
import pytz
+from FlaskRTBCTF.users.forms import (RegistrationForm, LoginForm,
+ RequestResetForm, ResetPasswordForm)
+from FlaskRTBCTF.users.utils import send_reset_email
+from FlaskRTBCTF.config import organization, LOGGING
+from FlaskRTBCTF.models import User, Score, Machine
+
from flask import render_template, url_for, flash, redirect, request, Blueprint
from flask_login import login_user, current_user, logout_user, login_required
from FlaskRTBCTF import db, bcrypt
-from FlaskRTBCTF.config import organization, LOGGING
-from FlaskRTBCTF.models import User, Score, Machine
+
if LOGGING:
from FlaskRTBCTF.models import Logs
-from FlaskRTBCTF.users.forms import (RegistrationForm, LoginForm,
- RequestResetForm, ResetPasswordForm)
-from FlaskRTBCTF.users.utils import send_reset_email
users = Blueprint('users', __name__)
@@ -21,43 +23,68 @@
@users.route("/register", methods=['GET', 'POST'])
def register():
- box = Machine.query.filter(Machine.ip=="127.0.0.1").first()
if current_user.is_authenticated:
flash('Already Authenticated', 'info')
return redirect(url_for('main.home'))
+
+ box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
form = RegistrationForm()
+
if form.validate_on_submit():
- hashed_password = bcrypt.generate_password_hash(
- form.password.data).decode('utf-8')
- user = User(username=form.username.data,
- email=form.email.data, password=hashed_password)
- score = Score(user=user, userHash=False, rootHash=False, points=0, machine=box)
+ hashed_password = bcrypt.generate_password_hash(form.password.data) \
+ .decode('utf-8')
+ user = User(
+ username=form.username.data, email=form.email.data,
+ password=hashed_password
+ )
+ score = Score(
+ user=user, userHash=False,
+ rootHash=False, points=0, machine=box
+ )
if LOGGING:
- log = Logs(user=user, accountCreationTime=datetime.now(pytz.utc), visitedMachine=False, machineVisitTime=None, userSubmissionTime=None,
- rootSubmissionTime=None, userSubmissionIP=None, rootSubmissionIP=None)
+ log = Logs(
+ user=user, accountCreationTime=datetime.now(pytz.utc),
+ visitedMachine=False, machineVisitTime=None,
+ userSubmissionTime=None, rootSubmissionTime=None,
+ userSubmissionIP=None, rootSubmissionIP=None
+ )
db.session.add(log)
+
db.session.add(user)
db.session.add(score)
db.session.commit()
- flash('Your account has been created! You are now able to log in.', 'success')
+ flash(
+ 'Your account has been created! You are now able to log in.',
+ 'success'
+ )
return redirect(url_for('users.login'))
- return render_template('register.html', title='Register', form=form, organization=organization)
+
+ return render_template('register.html', title='Register',
+ form=form, organization=organization)
+
@users.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
flash('Already Authenticated', 'info')
return redirect(url_for('main.home'))
+
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
- if user and bcrypt.check_password_hash(user.password, form.password.data):
+ pw_chk = bcrypt.check_password_hash(user.password, form.password.data)
+ if user and pw_chk:
login_user(user, remember=form.remember.data, force=True)
next_page = request.args.get('next')
- return redirect(next_page) if next_page else redirect(url_for('main.home'))
+ return redirect(next_page) if next_page else \
+ redirect(url_for('main.home'))
else:
- flash('Login Unsuccessful. Please check username and password.', 'danger')
- return render_template('login.html', title='Login', form=form, organization=organization)
+ flash(
+ 'Login Unsuccessful. Please check username and password.',
+ 'danger'
+ )
+ return render_template('login.html', title='Login',
+ form=form, organization=organization)
@users.route("/logout")
@@ -71,7 +98,8 @@ def logout():
@users.route("/account")
@login_required
def account():
- return render_template('account.html', title='Account', organization=organization)
+ return render_template('account.html', title='Account',
+ organization=organization)
@users.route("/reset_password", methods=['GET', 'POST'])
@@ -82,28 +110,37 @@ def reset_request():
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
send_reset_email(user)
- flash('An email has been sent with instructions to reset your password.', 'info')
+ flash(
+ 'An email has been sent with instructions to reset your password.',
+ 'info'
+ )
return redirect(url_for('users.login'))
- return render_template('reset_request.html', title='Reset Password', form=form, organization=organization)
+ return render_template('reset_request.html', title='Reset Password',
+ form=form, organization=organization)
@users.route("/reset_password/", methods=['GET', 'POST'])
def reset_token(token):
if current_user.is_authenticated:
return redirect(url_for('main.home'))
-
+
user = User.verify_reset_token(token)
-
+
if user is None:
flash('That is an invalid or expired token', 'warning')
return redirect(url_for('users.reset_request'))
form = ResetPasswordForm()
-
+
if form.validate_on_submit():
- hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
+ hashed_password = bcrypt.generate_password_hash(form.password.data) \
+ .decode('utf-8')
user.password = hashed_password
db.session.commit()
- flash('Your password has been updated! You are now able to log in', 'success')
+ flash(
+ 'Your password has been updated! You are now able to log in',
+ 'success'
+ )
return redirect(url_for('users.login'))
-
- return render_template('reset_token.html', title='Reset Password', form=form, organization=organization)
+
+ return render_template('reset_token.html', title='Reset Password',
+ form=form, organization=organization)
diff --git a/src/FlaskRTBCTF/users/utils.py b/src/FlaskRTBCTF/users/utils.py
index d99f057..f3ae0c9 100644
--- a/src/FlaskRTBCTF/users/utils.py
+++ b/src/FlaskRTBCTF/users/utils.py
@@ -4,6 +4,7 @@
from flask_mail import Message
from FlaskRTBCTF import mail
+
def send_reset_email(user):
token = user.get_reset_token()
msg = Message('Password Reset Request',
@@ -12,6 +13,7 @@ def send_reset_email(user):
msg.body = f'''To reset your password, visit the following link:
{url_for('users.reset_token', token=token, _external=True)}
-If you did not make this request then simply ignore this email and no changes will be made.
+If you did not make this request then simply ignore this email
+and no changes will be made.
'''
mail.send(msg)
diff --git a/src/create_db.py b/src/create_db.py
index 5f9b6a3..992c285 100644
--- a/src/create_db.py
+++ b/src/create_db.py
@@ -4,10 +4,10 @@
from FlaskRTBCTF import create_app, db, bcrypt
from FlaskRTBCTF.models import User, Score, Notification, Machine
from FlaskRTBCTF.config import organization, LOGGING
-
if LOGGING:
from FlaskRTBCTF.models import Logs
+
app = create_app()
# create_app().app_context().push()
@@ -15,11 +15,11 @@
db.create_all()
default_time = datetime.now(pytz.utc)
-
+
box = Machine(
name="My Awesome Pwnable Box",
- user_hash='A'*32,
- root_hash='B'*32,
+ user_hash='A' * 32,
+ root_hash='B' * 32,
user_points=10,
root_points=20,
os="Linux",
diff --git a/src/requirements.txt b/src/requirements.txt
index 8131eab..7449900 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -21,3 +21,4 @@ six==1.14.0
SQLAlchemy==1.3.16
Werkzeug==1.0.1
WTForms==2.2.1
+pytest-flake8==1.0.4
diff --git a/src/run.py b/src/run.py
index e8684df..4149120 100644
--- a/src/run.py
+++ b/src/run.py
@@ -3,4 +3,4 @@
app = create_app()
if __name__ == '__main__':
- app.run(debug=app.config.get('DEBUG', False))
\ No newline at end of file
+ app.run(debug=app.config.get('DEBUG', False))