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))