Fixed issues with user authentication.

Improved setup and minor improvements.
This commit is contained in:
Dorian 2013-06-11 23:55:31 -04:00
parent 6bf54f9fcd
commit 9bb664b097
8 changed files with 80 additions and 66 deletions

View File

@ -1,11 +1,10 @@
Things To-Do Things To-Do
============ ============
* Build out setup.py and make it easy to distribute on PyPI.
* Build out user account management and registration. {Started} * Build out user account management and registration. {Started}
* Make new logo based on APS' Mr. Penguin. {Started} * Make new logo based on APS' Mr. Penguin. {Started}
* Build out simple to-do/project creation and management setup. * Build out simple to-do/project creation and management setup.
* Setup a nice clutter-free deployment option for rookeri.es itself. (In-progress) * Setup a nice clutter-free deployment option for rookeri.es itself. {In-progress}
* Build out documentation with Sphinx. * Build out documentation with Sphinx.
** Make Sphinx output and site look-and-feel match. ** Make Sphinx output and site look-and-feel match.
@ -20,3 +19,4 @@ Things Done
----------- -----------
* *2013 May 09* --- Transitioned COPYING to reStructured text. * *2013 May 09* --- Transitioned COPYING to reStructured text.
* *2013 June 11* --- Build out setup.py and make it easy to distribute on PyPI.

View File

@ -23,16 +23,14 @@ Admin module for Rookeries that adds some special utilities to run outside of th
@author: Dorian Pula <dorian.pula@amber-penguin-software.ca> @author: Dorian Pula <dorian.pula@amber-penguin-software.ca>
""" """
from rookeries.core.database import db_session, init_db from rookeries.core.database import init_db
import sys import sys
def rebuild_database(): def rebuild_database():
"""Re-synchronizes the database which is pretty useful for rapid development.""" """Re-synchronizes the database which is pretty useful for rapid development."""
from rookeries.core.models import populate_db_with_user_setup # TODO Add in some nice switches later...
init_db(True, True)
init_db()
populate_db_with_user_setup(db_session)
def main(command): def main(command):

View File

@ -28,6 +28,7 @@ Core module that handles database connections in Rookeri.es
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from rookeries.core.config import Config from rookeries.core.config import Config
engine = create_engine(Config().DATABASE_URI, convert_unicode=True) engine = create_engine(Config().DATABASE_URI, convert_unicode=True)
@ -37,18 +38,16 @@ Base = declarative_base()
Base.query = db_session.query_property() Base.query = db_session.query_property()
def init_db(): def init_db(drop_tables=False, populate_with_data=False):
"""Initializes the database and bindings to the models.""" """Initializes the database and bindings to the models. Also populates the models with initial data."""
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
import rookeries.core.models import rookeries.core.models
if drop_tables:
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
if drop_tables and populate_with_data:
def resync_db(): # TODO Add in some nice checks for database migration??
"""Re-synchronizes the database which is pretty useful for rapid development.""" rookeries.core.models.populate_db_with_init_models(db_session)
from rookeries.core.models import populate_db_with_user_setup
init_db()
populate_db_with_user_setup(db_session)

View File

@ -44,6 +44,9 @@ class User(Base):
full_name = Column(String(256)) full_name = Column(String(256))
email = Column(String(128), unique=True) email = Column(String(128), unique=True)
user_auth_id = Column(Integer, ForeignKey("user_authentication.id"))
user_authentication = relationship("UserAuthentication", backref="user", cascade="all, delete")
user_type_id = Column(Integer, ForeignKey('user_type.id')) user_type_id = Column(Integer, ForeignKey('user_type.id'))
user_type = relationship("UserType", backref=backref('users', order_by=id)) user_type = relationship("UserType", backref=backref('users', order_by=id))
@ -56,16 +59,12 @@ class User(Base):
return "<User('%s', '%s', '%s')>" % (self.username, self.email, self.full_name) return "<User('%s', '%s', '%s')>" % (self.username, self.email, self.full_name)
class UserAuthenticaton(Base): class UserAuthentication(Base):
""" """Model for storing and managing user authentication."""
Separate setup for storing and managing user authentication.
"""
__tablename__ = "user_authentication" __tablename__ = "user_authentication"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("user.id"))
user = relationship("User", backref=backref("user_authentication", order_by=id))
password = Column(String(512)) password = Column(String(512))
security_update_date = Column(DateTime) security_update_date = Column(DateTime)
@ -73,9 +72,8 @@ class UserAuthenticaton(Base):
status_id = Column(Integer, ForeignKey("status.id")) status_id = Column(Integer, ForeignKey("status.id"))
status = relationship("Status") status = relationship("Status")
def __init__(self, user): def __init__(self):
self.user = user self.status = Status.query.filter(Status.name == "USER_UNKNOWN").first()
self.status = db_session.query(Status, name="USER_UNKNOWN")
self.security_update_date = datetime.now() self.security_update_date = datetime.now()
@ -114,9 +112,7 @@ class Status(Base):
# TODO Organize later... # TODO Organize later...
def populate_db_with_user_setup(db_session): def populate_db_with_init_models(db_session):
# TODO Make something nicer that queries before adding in a new entry...
# Add in types for users. # Add in types for users.
admin_user_type = UserType("admin", True, True) admin_user_type = UserType("admin", True, True)

View File

@ -31,50 +31,50 @@ from datetime import datetime
from rookeries.core.config import Config from rookeries.core.config import Config
def generate_password_hash(username, user_auth, password, site_secret): def generate_password_hash(user, password, site_secret):
""" """
Generates a proper secure salt with hash attached to a user. Generates a proper secure salt with hash attached to a user.
""" """
# Inspired by this article on proper handling of salts: http://crackstation.net/hashing-security.htm # Inspired by this article on proper handling of salts: http://crackstation.net/hashing-security.htm
user_sec_update_date = datetime.isoformat(user_auth.security_update_date) user_sec_update_date = datetime.isoformat(user.user_authentication.security_update_date)
hashable = username + ";" + password + ";" + site_secret + ";" + user_sec_update_date hashable = user.username + ";" + password + ";" + site_secret + ";" + user_sec_update_date
hasher = hashlib.sha512() hasher = hashlib.sha512()
hasher.update(hashable) hasher.update(hashable)
return unicode(hasher.hexdigest()) return unicode(hasher.hexdigest())
def check_password(username, user_auth, password): def check_password(user, password):
valid_password = False valid_password = False
config = Config() config = Config()
if user_auth is not None: if user is not None:
attempted_credentials = generate_password_hash(username, user_auth, password, config.SITE_SECRET) attempted_credentials = generate_password_hash(user, password, config.SITE_SECRET)
actual_credentials = user_auth.password actual_credentials = user.user_authentication.password
valid_password = (actual_credentials == attempted_credentials) valid_password = (actual_credentials == attempted_credentials)
return valid_password return valid_password
def change_password(username, user_auth, password): def change_password(user, password):
""" """
Generate the password hash and change it in the object. Generate the password hash and change it in the object.
""" """
user_auth.security_update_date = datetime.now() user.user_authentication.security_update_date = datetime.now()
hashed_password = generate_password_hash(username, user_auth, password, Config.SITE_SECRET) hashed_password = generate_password_hash(user, password, Config.SITE_SECRET)
user_auth.password = hashed_password user.user_authentication.password = hashed_password
def check_user_login(user, user_auth, password): def check_user_login(user, password):
""" """
Checks to see if the user can login. Checks to see if the user can login.
""" """
if check_password(user, user_auth, password): if check_password(user, password):
# TODO Use a better thing than just magic strings to check status. # TODO Use a better thing than just magic strings to check status.
return user_auth.status.name == "USER_ACTIVE" return user.user_authentication.status.name == "USER_ACTIVE"
else: else:
return False return False

View File

@ -1,15 +0,0 @@
from setuptools import setup
# TODO Add in comments plus all requistes from requirements.txt
# TODO Look into http://stackoverflow.com/questions/4150423/can-pip-install-dependencies-not-specified-in-setup-py-at-install-time
setup(name='rookeries',
version='0.2',
description='Python Distribution Utilities',
author='Dorian Pula',
author_email='dorian.pula@amber-penguin-software.ca',
url='http://rookeri.es',
packages=['distutils', 'distutils.command'],
install_requires= open('app/requirements.txt').readlines(),
# install_requires=['setuptools'],
)

View File

@ -30,7 +30,7 @@ import os
import datetime import datetime
from rookeries.core.config import Config from rookeries.core.config import Config
from rookeries.core.database import db_session from rookeries.core.database import db_session
from rookeries.core.models import User, UserAuthenticaton from rookeries.core.models import User, UserAuthentication
from rookeries.core.security import check_password, generate_password_hash from rookeries.core.security import check_password, generate_password_hash
app = Flask(__name__) app = Flask(__name__)
@ -53,10 +53,9 @@ def login():
# TODO Do try / catch or deal with exceptions? # TODO Do try / catch or deal with exceptions?
user = User.query.filter(User.username == username).first() user = User.query.filter(User.username == username).first()
user_auth = UserAuthenticaton.query.filter(UserAuthenticaton.user == user).first()
# TODO Do try / catch or deal with exceptions? # TODO Do try / catch or deal with exceptions?
valid_password = check_password(user, user_auth, password) valid_password = check_password(user, password)
if valid_password is True: if valid_password is True:
# TODO Add in proper authentication tokens instead... # TODO Add in proper authentication tokens instead...
session["logged_in"] = True session["logged_in"] = True
@ -163,11 +162,10 @@ def register_user():
# TODO Add in validation of email... and status that requires email validation of user. # TODO Add in validation of email... and status that requires email validation of user.
user = User(username=username, full_name=user_full_name, email=email) user = User(username=username, full_name=user_full_name, email=email)
user.user_authentication = UserAuthentication()
user.security_update_date = datetime.datetime.now()
hashed_password = generate_password_hash(user, password, Config.SITE_SECRET) hashed_password = generate_password_hash(user, password, Config.SITE_SECRET)
user.password = hashed_password user.user_authentication.password = hashed_password
# TODO Fix user types... not working very well at the moment... as in not at all. # TODO Fix user types... not working very well at the moment... as in not at all.
# user.user_type = UserType.id # user.user_type = UserType.id

38
setup.py Executable file
View File

@ -0,0 +1,38 @@
#
# Copyright (c) 2013 Dorian Pula <dorian.pula@amber-penguin-software.ca>
#
# Rookeries is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# Rookeries is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with Rookeries. If not, see <http://www.gnu.org/licenses/>.
#
# Please share and enjoy!
#
"""
Setup for Rookeries web app
@author: Dorian Pula <dorian.pula@amber-penguin-software.ca>
"""
from setuptools import setup
# Inspired by
# http://stackoverflow.com/questions/4150423/can-pip-install-dependencies-not-specified-in-setup-py-at-install-time
setup(name='rookeries',
version='0.0.2',
description='Rookeri.es Task Centric Productivity Suite',
author='Dorian Pula',
author_email='dorian.pula@amber-penguin-software.ca',
url='http://rookeri.es/',
packages=['rookeries', 'rookeries.core'],
install_requires=open('requirements.txt').readlines(),)