about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSam2013-10-07 02:14:41 -0500
committerSam2013-10-07 02:14:41 -0500
commita55a1b941864a9574b8177349b8c9c750f379c72 (patch)
treec0e0e1ea8ad7853d0d66e6a7b6bead5eaf01046c
parente13f0c576ca75af57abac83851d206e113dabaad (diff)
downloadgenenetwork2-a55a1b941864a9574b8177349b8c9c750f379c72.tar.gz
Worked on logins, session_ids, flash messages, etc.
-rw-r--r--wqflask/secure_server.py7
-rwxr-xr-xwqflask/utility/__init__.py23
-rw-r--r--wqflask/wqflask/model.py12
-rw-r--r--wqflask/wqflask/templates/base.html29
-rw-r--r--wqflask/wqflask/templates/index_page.html7
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html144
-rw-r--r--wqflask/wqflask/templates/new_security/thank_you.html4
-rw-r--r--wqflask/wqflask/user_manager.py94
-rw-r--r--wqflask/wqflask/views.py43
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'''