CTF Platform API basic auth

Addded ctf platform Flask app with basic auth functions
register, login, logout
Added ctf platform database design (MySQL Workbench)
Added forward engineered SQL file
Added wsgi startup script
Installed various python dependencies
README.md Updated
.gitignore Updated
This commit is contained in:
Jan Groß
2018-07-14 20:55:46 +02:00
parent d85fca0ef4
commit 0838a0a5ce
15 changed files with 303 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env python
import main

96
ctf_platform/api/auth.py Normal file
View File

@@ -0,0 +1,96 @@
from flask import Blueprint, g, redirect, request, session, url_for, jsonify
from flask import current_app as app
from werkzeug.security import check_password_hash, generate_password_hash
from .database import get_db
from .helper_functions import build_response_dict
import functools
bp = Blueprint('auth', __name__, url_prefix='/auth/')
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
db = get_db()
db.execute(
'SELECT user_id, username, active, registered, info FROM user WHERE user_id = %s', (user_id)
)
g.user = db.fetchone()
app.logger.info('USER ACCESS %s' % (g.user))
#@auth.login_required decorator
def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwagrs):
if g.user is None:
api_response = build_response_dict(False, 'access', 'Access denied', reason='Login required')
return jsonify(api_response)
return view(**kwagrs)
return wrapped_view
@bp.route("/register", methods=['POST'])
def register():
api_response = {}
error = None
username = request.form.get('username')
password = request.form.get('password')
db = get_db()
if not username:
error = 'Username required!'
elif not password:
error = 'Password required!'
else:
db.execute('SELECT user_id FROM user WHERE username = %s', (username))
if db.fetchone():
error = "Username already exists!"
if error is None:
password_hash = generate_password_hash(password)
db.execute('INSERT INTO user (username, password) VALUES (%s, %s)', (username, password_hash))
g.db.commit()
api_response = build_response_dict(True, 'register', 'Registration successful')
else:
api_response = build_response_dict(False, 'register', 'Registration failed', reason=error)
return jsonify(api_response)
@bp.route("/login", methods=['POST'])
def login():
api_response = {}
error = None
username = request.form.get('username')
password = request.form.get('password')
if not username:
error = 'Username required!'
elif not password:
error = 'Password required!'
else:
db = get_db()
db.execute("SELECT * FROM user WHERE username = %s", (username))
db_user = db.fetchone()
if db_user is None or not check_password_hash(db_user['password'], password):
error = 'Username or password incorrect!'
if error is None:
session.clear()
session['user_id'] = db_user['user_id']
session['user_name'] = db_user['username']
api_response = build_response_dict(True, 'login', 'Login successful')
app.logger.info("{} logged in successfully!".format(username))
else:
api_response = build_response_dict(False, 'login', 'Login failed', reason=error)
app.logger.info("{} failed to login ({})!".format(username, error))
return jsonify(api_response)
@bp.route('/logout', methods=['GET'])
def logout():
if 'user_name' in session:
app.logger.info("{} logged out!".format(session['user_name']))
session.clear()
api_response = build_response_dict(True, 'logout', 'Logout successful')
else:
api_response = build_response_dict(False, 'logout', 'Logout failed', reason='No active session')
return jsonify(api_response)

View File

@@ -0,0 +1,50 @@
import pymysql, click, re
from flask import current_app as app
from flask import g
from flask.cli import with_appcontext
def get_db():
if 'db' not in g:
mysql = pymysql.connect(host=app.config['MYSQL_DATABASE_HOST'],
user=app.config['MYSQL_DATABASE_USER'],
password=app.config['MYSQL_DATABASE_PASS'],
db=app.config['MYSQL_DATABASE_DB'],
cursorclass=pymysql.cursors.DictCursor)
g.db = mysql
g.db_cursor = mysql.cursor()
return g.db_cursor
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
def init_db(schema):
db = mysql = pymysql.connect(host=app.config['MYSQL_DATABASE_HOST'],
user=app.config['MYSQL_DATABASE_USER'],
password=app.config['MYSQL_DATABASE_PASS'])
statement = ""
for line in open(schema):
if line.strip().startswith('--'): # ignore sql comment lines
continue
if not line.strip().endswith(';'): # keep appending lines that don't end in ';'
statement = statement + line
else: # when you get a line ending in ';' then exec statement and reset for next statement
statement = statement + line
try:
db.cursor().execute(statement)
except Exception as e:
print("[WARN] MySQLError during execute statement \n\tArgs: '{}'".format(str(e.args)))
statement = ""
@click.command('init-db')
@click.argument('schema')
@with_appcontext
def init_db_command(schema):
init_db(schema)
click.echo('Initialized the database.')
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)

1
ctf_platform/api/flag.py Normal file
View File

@@ -0,0 +1 @@
#Flag blueprint

View File

@@ -0,0 +1,11 @@
def build_response_dict(success, action, message, reason=None):
response_dict = {}
if success:
response_dict['data'] = { action: 'success', 'message': message}
if reason is not None:
response_dict['data']['reason'] = reason
elif not success:
response_dict['error'] = { action: 'failed', 'message': message}
if reason is not None:
response_dict['error']['reason'] = reason
return response_dict

17
ctf_platform/api/main.py Normal file
View File

@@ -0,0 +1,17 @@
from flask import Flask, jsonify
import os, logging, sys
def create_app():
app = Flask(__name__)
app.config.from_pyfile('../config.cfg')
app.secret_key = os.urandom(24)
from . import auth, database
database.init_app(app)
app.register_blueprint(auth.bp)
@app.route("/")
def home():
info = { 'data' : { 'version': '1.0' }}
return jsonify(info)
return app

5
ctf_platform/config.cfg Normal file
View File

@@ -0,0 +1,5 @@
MYSQL_DATABASE_HOST = 'localhost'
MYSQL_DATABASE_USER = 'ctf_user'
MYSQL_DATABASE_PASS = ''
MYSQL_DATABASE_DB = 'ctf_main'
DEBUG = True

BIN
ctf_platform/ctf_db.mwb Normal file

Binary file not shown.

BIN
ctf_platform/database.db Normal file

Binary file not shown.

70
ctf_platform/schema.sql Normal file
View File

@@ -0,0 +1,70 @@
-- MySQL Workbench Forward Engineering
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
-- -----------------------------------------------------
-- Schema ctf_main
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `ctf_main` ;
-- -----------------------------------------------------
-- Schema ctf_main
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `ctf_main` DEFAULT CHARACTER SET utf8 ;
USE `ctf_main` ;
-- -----------------------------------------------------
-- Table `ctf_main`.`challenge`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `ctf_main`.`challenge` (
`challenge_id` INT NOT NULL,
`challenge_name` VARCHAR(45) NOT NULL,
`challenge_description` VARCHAR(45) NULL,
`challenge_flag` VARCHAR(45) NOT NULL,
`challenge_score` INT NOT NULL,
PRIMARY KEY (`challenge_id`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `ctf_main`.`user`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `ctf_main`.`user` (
`user_id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`password` VARCHAR(160) NOT NULL,
`active` TINYINT NOT NULL DEFAULT 1,
`registered` DATETIME NULL DEFAULT NOW(),
`info` VARCHAR(500) NULL,
PRIMARY KEY (`user_id`),
UNIQUE INDEX `user_id_UNIQUE` (`user_id` ASC))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `ctf_main`.`solved_challenges`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `ctf_main`.`solved_challenges` (
`user_id` INT NOT NULL,
`challenge_id` INT NOT NULL,
`submission_timestamp` DATETIME NOT NULL,
PRIMARY KEY (`user_id`),
INDEX `fk_challenge_id_idx` (`challenge_id` ASC),
CONSTRAINT `fk_user_id`
FOREIGN KEY (`user_id`)
REFERENCES `ctf_main`.`user` (`user_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_challenge_id`
FOREIGN KEY (`challenge_id`)
REFERENCES `ctf_main`.`challenge` (`challenge_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

2
ctf_platform/start_api.sh Executable file
View File

@@ -0,0 +1,2 @@
gunicorn wsgi:app -b localhost:4910

2
ctf_platform/wsgi.py Normal file
View File

@@ -0,0 +1,2 @@
from api import main
app = main.create_app()