From 511e16c514ecccf6f42514c46cea5774e1517479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Sat, 28 Apr 2018 19:08:29 +0200 Subject: [PATCH] Perform mercilless separation of bussiness logic from request handling --- solvable/src/webservice/crypto.py | 11 +++++++ solvable/src/webservice/exceptions.py | 6 ++++ solvable/src/webservice/model.py | 41 ++++++++++----------------- solvable/src/webservice/server.py | 39 +++++++++++++------------ solvable/src/webservice/user_ops.py | 28 ++++++++++++++++++ 5 files changed, 81 insertions(+), 44 deletions(-) create mode 100644 solvable/src/webservice/crypto.py create mode 100644 solvable/src/webservice/exceptions.py create mode 100644 solvable/src/webservice/user_ops.py diff --git a/solvable/src/webservice/crypto.py b/solvable/src/webservice/crypto.py new file mode 100644 index 0000000..9ccf1cf --- /dev/null +++ b/solvable/src/webservice/crypto.py @@ -0,0 +1,11 @@ +from passlib.hash import pbkdf2_sha256 + + +class PasswordHasher: + @staticmethod + def hash(password): + return pbkdf2_sha256.hash(password) + + @staticmethod + def verify(password, hash): + return pbkdf2_sha256.verify(password, hash) diff --git a/solvable/src/webservice/exceptions.py b/solvable/src/webservice/exceptions.py new file mode 100644 index 0000000..2421580 --- /dev/null +++ b/solvable/src/webservice/exceptions.py @@ -0,0 +1,6 @@ +class InvalidCredentialsError(RuntimeError): + pass + + +class UserExistsError(RuntimeError): + pass diff --git a/solvable/src/webservice/model.py b/solvable/src/webservice/model.py index f480b03..efdaf86 100644 --- a/solvable/src/webservice/model.py +++ b/solvable/src/webservice/model.py @@ -1,12 +1,26 @@ from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -from passlib.hash import pbkdf2_sha256 engine = create_engine('sqlite:///db.db', convert_unicode=True) +Base = declarative_base() + + +class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + username = Column(String, nullable=False, unique=True) + passwordhash = Column(String, nullable=False) + + +def init_db(): + Base.metadata.create_all(bind=engine) + + class Session: session = None @@ -23,28 +37,3 @@ class Session: def __exit__(self, exc_type, exc_val, exc_tb): self.session.close() - - -Base = declarative_base() - - -class User(Base): - __tablename__ = 'users' - - id = Column(Integer, primary_key=True) - username = Column(String, nullable=False, unique=True) - passwordhash = Column(String, nullable=False) - - -def init_db(): - Base.metadata.create_all(bind=engine) - - -class PasswordHasher: - @staticmethod - def hash(password): - return pbkdf2_sha256.hash(password) - - @staticmethod - def verify(password, hash): - return pbkdf2_sha256.verify(password, hash) diff --git a/solvable/src/webservice/server.py b/solvable/src/webservice/server.py index cde4b42..f825468 100644 --- a/solvable/src/webservice/server.py +++ b/solvable/src/webservice/server.py @@ -2,7 +2,9 @@ from os import urandom, getenv from flask import Flask, render_template, request, session, url_for -from model import init_db, User, Session, PasswordHasher +from model import init_db +from user_ops import UserOps +from exceptions import InvalidCredentialsError, UserExistsError BASEURL = getenv('BASEURL', '') init_db() @@ -18,15 +20,15 @@ app.jinja_env.globals.update(get_url=get_url) @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'POST': - with Session() as db: - user = db.query(User).filter(User.username == request.form['username']).first() + try: + UserOps(request.form.get('username'), + request.form.get('password')).authenticate() + except InvalidCredentialsError: + return render_template('login.html', alert='Invalid credentials!') - if not user or not PasswordHasher.verify(request.form['password'], user.passwordhash): - return render_template('login.html', alert='Invalid credentials!') - - session['logged_in'] = True - session['username'] = request.form['username'] - return render_template('internal.html') + session['logged_in'] = True + session['username'] = request.form['username'] + return render_template('internal.html') if session.get('logged_in'): return render_template('internal.html') @@ -36,20 +38,21 @@ def index(): @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': - if not all([request.form.get('username'), request.form.get('password'), request.form.get('passwordconfirm')]): + if not all([request.form.get('username'), + request.form.get('password'), + request.form.get('passwordconfirm')]): return render_template('register.html', alert='You need to fill everything.') if request.form['password'] != request.form['passwordconfirm']: return render_template('register.html', alert='Passwords do not match! Please try again.') - with Session() as db: - if db.query(User).filter(User.username == request.form['username']).all(): - return render_template('register.html', alert='Username already in use.') + try: + UserOps(request.form.get('username'), + request.form.get('password')).register() + except UserExistsError: + return render_template('register.html', alert='Username already in use.') - db.add(User(username=request.form['username'], - passwordhash=PasswordHasher.hash(request.form['password']))) - db.commit() - - return render_template('login.html', success='Account "{}" successfully registered. You can log in now!'.format(request.form['username'])) + return render_template('login.html', success=('Account "{}" successfully registered. ' + 'You can log in now!'.format(request.form['username']))) return render_template('register.html') diff --git a/solvable/src/webservice/user_ops.py b/solvable/src/webservice/user_ops.py new file mode 100644 index 0000000..e90a8bd --- /dev/null +++ b/solvable/src/webservice/user_ops.py @@ -0,0 +1,28 @@ +from crypto import PasswordHasher +from model import Session, User +from exceptions import InvalidCredentialsError, UserExistsError + + +class UserOps: + def __init__(self, username, password): + self.username = username + self.password = password + + def authenticate(self): + with Session() as db: + + user = db.query(User).filter(User.username == self.username).first() + + if not user or not PasswordHasher.verify(self.password, user.passwordhash): + raise InvalidCredentialsError + + def register(self): + with Session() as db: + + if db.query(User).filter(User.username == self.username).all(): + raise UserExistsError + + user = User(username=self.username, + passwordhash=PasswordHasher.hash(self.password)) + db.add(user) + db.commit()