diff options
-rw-r--r-- | wqflask/secure_server.py | 7 | ||||
-rwxr-xr-x | wqflask/utility/__init__.py | 23 | ||||
-rw-r--r-- | wqflask/wqflask/model.py | 12 | ||||
-rw-r--r-- | wqflask/wqflask/templates/base.html | 29 | ||||
-rw-r--r-- | wqflask/wqflask/templates/index_page.html | 7 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/login_user.html | 144 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/thank_you.html | 4 | ||||
-rw-r--r-- | wqflask/wqflask/user_manager.py | 94 | ||||
-rw-r--r-- | wqflask/wqflask/views.py | 43 |
9 files changed, 260 insertions, 103 deletions
diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py index df195bd2..a77abf7e 100644 --- a/wqflask/secure_server.py +++ b/wqflask/secure_server.py @@ -25,8 +25,11 @@ app.logger.addHandler(file_handler) import logging_tree logging_tree.printout() -import sys -print("At startup, path is:", sys.path) +#import sys +#print("At startup, path is:", sys.path) + +from werkzeug.contrib.fixers import ProxyFix +app.wsgi_app = ProxyFix(app.wsgi_app) #print("app.config is:", app.config) diff --git a/wqflask/utility/__init__.py b/wqflask/utility/__init__.py index d0e4a3fa..d9856eed 100755 --- a/wqflask/utility/__init__.py +++ b/wqflask/utility/__init__.py @@ -1,5 +1,6 @@ from pprint import pformat as pf +# Todo: Move these out of __init__ class Bunch(object): """Like a dictionary but using object notation""" @@ -10,3 +11,25 @@ class Bunch(object): return pf(self.__dict__) +class Struct(object): + '''The recursive class for building and representing objects with. + + From http://stackoverflow.com/a/6573827/1175849 + + ''' + + def __init__(self, obj): + for k, v in obj.iteritems(): + if isinstance(v, dict): + setattr(self, k, Struct(v)) + else: + setattr(self, k, v) + + def __getitem__(self, val): + return self.__dict__[val] + + def __repr__(self): + return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for + (k, v) in self.__dict__.iteritems())) + + diff --git a/wqflask/wqflask/model.py b/wqflask/wqflask/model.py index 8e7a823e..5c514bde 100644 --- a/wqflask/wqflask/model.py +++ b/wqflask/wqflask/model.py @@ -1,7 +1,9 @@ from __future__ import print_function, division, absolute_import import uuid +import datetime +from flask import request from flask.ext.sqlalchemy import SQLAlchemy #from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin @@ -84,9 +86,15 @@ class Login(Base): __tablename__ = "login" id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4())) user = Column(Unicode(36), ForeignKey('user.id')) - timestamp = Column(DateTime()) + timestamp = Column(DateTime(), default=lambda: datetime.datetime.utcnow()) ip_address = Column(Unicode(39)) - + successful = Column(Boolean(), nullable=False) # False if wrong password was entered + session_id = Column(Text) # Set only if successfully logged in, otherwise should be blank + + def __init__(self, user): + self.user = user.id + self.ip_address = request.remote_addr + # Setup Flask-Security #user_datastore = SQLAlchemyUserDatastore(db, User, Role) diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 6e7119fe..077e4705 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -26,10 +26,39 @@ </head> +{% macro header(main, second) %} + <header class="jumbotron subhead" id="overview"> + <div class="container"> + <h1>Login</h1> + <p class="lead"> + Gain access to GeneNetwork. + </p> + </div> + </header> + + {{ flash_me() }} +{% endmacro %} + + +{% macro flash_me() -%} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + <br /> + <div class="container"> + {% for category, message in messages %} + <div class="alert {{ category }}">{{ message }}</div> + {% endfor %} + </div> + {% endif %} + {% endwith %} +{% endmacro %} + <body data-spy="scroll" data-target=".bs-docs-sidebar"> <!-- Navbar ================================================== --> + <div class="navbar navbar-inverse navbar-fixed-top"> + <div class="navbar-inner"> <div class="container"> <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> diff --git a/wqflask/wqflask/templates/index_page.html b/wqflask/wqflask/templates/index_page.html index 61e34eca..98682e57 100644 --- a/wqflask/wqflask/templates/index_page.html +++ b/wqflask/wqflask/templates/index_page.html @@ -8,11 +8,14 @@ <header class="jumbotron subhead" id="overview"> <div class="container"> <h1>GeneNetwork</h1> - <p class="lead">Open source bioinformatics for systems genetics</p> + <p class="lead">Open source bioinformatics for systems genetics</p> </div> </header> <div class="container"> + + {{ flash_me() }} + <div class="row"> <div class="span3 bs-docs-sidebar"> <ul class="nav nav-list bs-docs-sidenav"> @@ -24,7 +27,7 @@ <li><a href="#websites"><i class="icon-chevron-right"></i> Affiliates and mirrors</a></li> </ul> </div> - + <div class="span9"> <section id="quick-search"> <div class="page-header"> diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html index 360a6a68..f232ccbc 100644 --- a/wqflask/wqflask/templates/new_security/login_user.html +++ b/wqflask/wqflask/templates/new_security/login_user.html @@ -1,61 +1,83 @@ -<div class="security_box"> - - <h4>* Don't have an account?</h4> - - <center> - <a href="/n/register" class="btn btn-info modalize">Create a new account</a> - </center> - - <hr /> - - <h4>Already have an account?</h4> - - <h5>Sign in here</h5> - - <form class="form-horizontal" action="/n/login_submit" - method="POST" name="login_user_form"> - <fieldset> - - - <div class="control-group"> - <label class="control-label" for="email">Email Address</label> - <div class="controls"> - <input id="email" class="focused" name="email" type="text" value=""> - </div> - </div> - - <div class="control-group"> - <label class="control-label" for="password">Password</label> - <div class="controls"> - <input id="password" name="password" type="password" value=""> - <br /> - <a href="{{ url_for_security('forgot_password') }}">Forgot your password?</a><br/> - </div> - </div> - - - <div class="control-group"> - <div class="controls"> - <label class="checkbox"> - <input id="remember" name="remember" type="checkbox" value="y"> Remember me - </label> - </div> - - - <div class="control-group"> - <div class="controls"> - <input id="next" name="next" type="hidden" value=""> - - <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in"> - </div> - - - </div> - </fieldset> - - </form> -</div> - - -{% include "new_security/_scripts.html" %} -<!--{% include "security/_menu.html" %}--> +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + {{ header("Login", "Gain access to GeneNetwork.") }} + + <div class="container"> + <div class="page-header"> + <h1>Login</h1> + </div> + + + <div class="security_box"> + + <h4>Don't have an account?</h4> + + + <a href="/n/register" class="btn btn-info modalize">Create a new account</a> + + + <hr /> + + <h4>Already have an account?</h4> + + <h5>Sign in here</h5> + + + + <form class="form-horizontal" action="/n/login" + method="POST" name="login_user_form"> + <fieldset> + + + <div class="control-group"> + <label class="control-label" for="email_address">Email Address</label> + <div class="controls"> + <input id="email_address" class="focused" name="email_address" type="text" value=""> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="password">Password</label> + <div class="controls"> + <input id="password" name="password" type="password" value=""> + <br /> + <a href="url_for_security('forgot_password')">Forgot your password?</a><br/> + </div> + </div> + + + <div class="control-group"> + <div class="controls"> + <label class="checkbox"> + <input id="remember" name="remember" type="checkbox" value="y"> Remember me + </label> + </div> + + + <div class="control-group"> + <div class="controls"> + <input id="next" name="next" type="hidden" value=""> + + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in"> + </div> + + + </div> + </fieldset> + + </form> + </div> + </div> + </div> + + {% endblock %} + +{% block js %} + <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>--> + + {% include "new_security/_scripts.html" %} + +{% endblock %} + diff --git a/wqflask/wqflask/templates/new_security/thank_you.html b/wqflask/wqflask/templates/new_security/thank_you.html index 5aa11ebf..97cb7807 100644 --- a/wqflask/wqflask/templates/new_security/thank_you.html +++ b/wqflask/wqflask/templates/new_security/thank_you.html @@ -12,12 +12,12 @@ <div class="container"> <div class="page-header"> - <h1>All done</h1> + <h3>You are done registering</h3> </div> <p>Enjoy using the site.</p> - <p>Go to the <a href="{{ url_for("/") }}">homepage</a></p>. + <p>Go to the <a href="{{ url_for("index_page") }}">homepage</a></p>. </div> {% endblock %} diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py index b967c86f..7c1761ba 100644 --- a/wqflask/wqflask/user_manager.py +++ b/wqflask/wqflask/user_manager.py @@ -15,6 +15,7 @@ import time import uuid import hashlib import hmac +import base64 import simplejson as json @@ -22,7 +23,7 @@ from redis import StrictRedis Redis = StrictRedis() -from flask import Flask, g, render_template, url_for, request +from flask import Flask, g, render_template, url_for, request, make_response, redirect, flash from wqflask import app @@ -35,7 +36,7 @@ from wqflask.database import db_session from wqflask import model -from utility import Bunch +from utility import Bunch, Struct @@ -112,7 +113,9 @@ class RegisterUser(object): db_session.add(self.new_user) db_session.commit() + print("Adding verification email to queue") self.send_email_verification() + print("Added verification email to queue") self.thank_you_mode = True @@ -122,9 +125,10 @@ class RegisterUser(object): pwfields.algorithm = "pbkdf2" pwfields.hashfunc = "sha256" - hashfunc = getattr(hashlib, pwfields.hashfunc) + #hashfunc = getattr(hashlib, pwfields.hashfunc) - pwfields.salt = os.urandom(32) + # Encoding it to base64 makes storing it in json much easier + pwfields.salt = base64.b64encode(os.urandom(32)) # https://forums.lastpass.com/viewtopic.php?t=84104 pwfields.iterations = 100000 @@ -136,18 +140,18 @@ class RegisterUser(object): print("pwfields:", vars(pwfields)) print("locals:", locals()) + + enc_password = Password(password, + pwfields.salt, + pwfields.iterations, + pwfields.keylength, + pwfields.hashfunc) - # On our computer it takes around 1.4 seconds - start_time = time.time() - pwfields.password = pbkdf2.pbkdf2_hex(password, pwfields.salt, pwfields.iterations, pwfields.keylength, hashfunc) - pwfields.encrypt_time = round(time.time() - start_time, 3) - - print("Creating password took:", pwfields.encrypt_time) + pwfields.password = enc_password.password + pwfields.encrypt_time = enc_password.encrypt_time self.user.password = json.dumps(pwfields.__dict__, - sort_keys=True, - # See http://stackoverflow.com/a/12312896 - encoding="latin-1" + sort_keys=True, ) def send_email_verification(self): @@ -166,7 +170,21 @@ class RegisterUser(object): body = render_template("email/verification.txt", verification_code = verification_code) send_email(to, subject, body) - + +class Password(object): + def __init__(self, unencrypted_password, salt, iterations, keylength, hashfunc): + print("in Password __init__ locals are:", locals()) + hashfunc = getattr(hashlib, hashfunc) + print("hashfunc is:", hashfunc) + # On our computer it takes around 1.4 seconds in 2013 + start_time = time.time() + salt = base64.b64decode(salt) + print("now salt is:", salt) + self.password = pbkdf2.pbkdf2_hex(str(unencrypted_password), + salt, iterations, keylength, hashfunc) + self.encrypt_time = round(time.time() - start_time, 3) + print("Creating password took:", self.encrypt_time) + def basic_info(): return dict(timestamp = timestamp(), @@ -184,8 +202,54 @@ def verify_email(): user.confirmed = json.dumps(basic_info(), sort_keys=True) db_session.commit() - +def login(): + params = request.form if request.form else request.args + print("in login params are:", params) + if not params: + return render_template("new_security/login_user.html") + else: + user = model.User.query.filter_by(email_address=params['email_address']).one() + submitted_password = params['password'] + pwfields = Struct(json.loads(user.password)) + encrypted = Password(submitted_password, + pwfields.salt, + pwfields.iterations, + pwfields.keylength, + pwfields.hashfunc) + print("\n\nComparing:\n{}\n{}\n".format(encrypted.password, pwfields.password)) + valid = pbkdf2.safe_str_cmp(encrypted.password, pwfields.password) + print("valid is:", valid) + + login_rec = model.Login(user) + + + if valid: + login_rec.successful = True + login_rec.session_id = str(uuid.uuid4()) + #session_id = "session_id:{}".format(login_rec.session_id) + session_id_signature = actual_hmac_creation(login_rec.session_id) + session_id_signed = login_rec.session_id + ":" + session_id_signature + + session = dict(login_time = time.time(), + user_id = user.id, + user_email_address = user.email_address) + + flash("Thank you for logging in.", "alert-success") + + Redis.hmset("session_id:" + login_rec.session_id, session) + + response = make_response(redirect('/')) + response.set_cookie('session_id', session_id_signed) + else: + login_rec.successful = False + flash("Invalid email-address or password. Please try again.", "alert-error") + response = make_response(redirect(url_for('login'))) + db_session.add(login_rec) + db_session.commit() + return response + def logout(): + pass ################################# Sign and unsign ##################################### diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index fe91e014..deccf459 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -267,20 +267,15 @@ def sharing_info_page(): template_vars = SharingInfoPage.SharingInfoPage(fd) return template_vars -# Take this out or secure it before g[umlfoing into production +# Take this out or secure it before putting into production @app.route("/get_temp_data") def get_temp_data(): temp_uuid = request.args['key'] return flask.jsonify(temp_data.TempData(temp_uuid).get_all()) -#@app.route("/thank_you") -#def thank_you(): -# return render_template("security/thank_you.html") -@app.route("/manage/verify") -def verify(): - user_manager.verify_email() - return render_template("new_security/verified.html") +################################################################################################### + @app.route("/manage/users") def manage_users(): @@ -298,16 +293,18 @@ def manage_groups(): return render_template("admin/group_manager.html", **template_vars.__dict__) - - @app.route("/n/register", methods=('GET', 'POST')) -def new_register(): +def register(): params = None errors = None - if request.form: - params = request.form - else: - params = request.args + + #if request.form: + # params = request.form + #else: + # params = request.args + + params = request.form if request.form else request.args + if params: print("Attempting to register the user...") result = user_manager.RegisterUser(params) @@ -315,7 +312,7 @@ def new_register(): if result.thank_you_mode: assert not errors, "Errors while in thank you mode? That seems wrong..." - return render_template("new_security/thank_you.html") + return render_template("new_security/registered.html") return render_template("new_security/register_user.html", values=params, errors=errors) @@ -333,9 +330,17 @@ def new_register(): # #return redirect(url_for('new_register', errors=errors), code=307) -@app.route("/n/login") -def new_login(): - return render_template("new_security/login_user.html") +@app.route("/n/login", methods=('GET', 'POST')) +def login(): + return user_manager.login() + +@app.route("/manage/verify") +def verify(): + user_manager.verify_email() + return render_template("new_security/verified.html") + + +########################################################################## def json_default_handler(obj): '''Based on http://stackoverflow.com/a/2680060/1175849''' |