aboutsummaryrefslogtreecommitdiff
path: root/wqflask
diff options
context:
space:
mode:
Diffstat (limited to 'wqflask')
-rw-r--r--wqflask/wqflask/model.py4
-rw-r--r--wqflask/wqflask/send_mail.py45
-rw-r--r--wqflask/wqflask/templates/email/verification.txt8
-rw-r--r--wqflask/wqflask/user_manager.py123
-rw-r--r--wqflask/wqflask/views.py5
5 files changed, 173 insertions, 12 deletions
diff --git a/wqflask/wqflask/model.py b/wqflask/wqflask/model.py
index 1f7297a2..a3cd63a5 100644
--- a/wqflask/wqflask/model.py
+++ b/wqflask/wqflask/model.py
@@ -10,7 +10,7 @@ from flask.ext.sqlalchemy import SQLAlchemy
from wqflask import app
-from sqlalchemy import Column, Integer, String, Table, ForeignKey, Unicode, Boolean, DateTime
+from sqlalchemy import Column, Integer, String, Table, ForeignKey, Unicode, Boolean, DateTime, Text
from sqlalchemy.orm import relationship, backref
from wqflask.database import Base
@@ -60,7 +60,7 @@ class User(Base):
email_address = Column(Unicode(50), unique=True, nullable=False)
# Todo: Turn on strict mode for Mysql
- password = Column(Unicode(500), nullable=False)
+ password = Column(Text, nullable=False)
full_name = Column(Unicode(50))
organization = Column(Unicode(50))
diff --git a/wqflask/wqflask/send_mail.py b/wqflask/wqflask/send_mail.py
new file mode 100644
index 00000000..be51ad0d
--- /dev/null
+++ b/wqflask/wqflask/send_mail.py
@@ -0,0 +1,45 @@
+from __future__ import absolute_import, division, print_function
+
+import datetime
+
+import simplejson as json
+
+from redis import StrictRedis
+Redis = StrictRedis()
+
+import mailer
+
+def timestamp():
+ ts = datetime.datetime.utcnow()
+ return ts.isoformat()
+
+def main():
+ while True:
+ print("Waiting for message to show up in queue...")
+ msg = Redis.blpop("mail_queue")
+
+ # Queue name is the first element, we want the second, which is the actual message
+ msg = msg[1]
+
+ print("\nGot a msg in queue at {}: {}".format(timestamp(), msg))
+ # Todo: Truncate mail_processed when it gets to long
+ Redis.rpush("mail_processed", msg)
+ process_message(msg)
+
+
+def process_message(msg):
+ msg = json.loads(msg)
+
+ message = mailer.Message()
+ message.From = msg['From']
+ message.To = msg['To']
+ message.Subject = msg['Subject']
+ message.Body = msg['Body']
+
+ sender = mailer.Mailer('localhost')
+ sender.send(message)
+ print("Sent message at {}: {}\n".format(timestamp(), msg))
+
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/wqflask/wqflask/templates/email/verification.txt b/wqflask/wqflask/templates/email/verification.txt
new file mode 100644
index 00000000..29229c68
--- /dev/null
+++ b/wqflask/wqflask/templates/email/verification.txt
@@ -0,0 +1,8 @@
+Thank you for signing up for GeneNetwork.
+
+We need to verify your email address.
+
+To do that please click the following link, or cut and paste it into your browser window:
+
+{{ url_for_hmac("verify", code = verification_code, _external=True )}}
+
diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py
index bafc1c3e..159a0ffc 100644
--- a/wqflask/wqflask/user_manager.py
+++ b/wqflask/wqflask/user_manager.py
@@ -12,8 +12,23 @@ import hashlib
import datetime
import time
+import uuid
+import hashlib
+import hmac
+
import simplejson as json
+from redis import StrictRedis
+Redis = StrictRedis()
+
+
+from flask import Flask, g, render_template, url_for
+
+from wqflask import app
+
+
+from pprint import pformat as pf
+
from wqflask import pbkdf2
from wqflask.database import db_session
@@ -22,9 +37,7 @@ from wqflask import model
from utility import Bunch
-from flask import Flask, g
-from pprint import pformat as pf
from base.data_set import create_datasets_list
@@ -89,21 +102,25 @@ class RegisterUser(object):
self.set_password(password)
- new_user = model.User(**self.user.__dict__)
- db_session.add(new_user)
+ self.new_user = model.User(**self.user.__dict__)
+ db_session.add(self.new_user)
db_session.commit()
+ self.send_email_verification()
+
def set_password(self, password):
pwfields = Bunch()
- algo_string = "sha256"
- algorithm = getattr(hashlib, algo_string)
- pwfields.algorithm = "pbkdf2-" + algo_string
+
+ pwfields.algorithm = "pbkdf2"
+ pwfields.hashfunc = "sha256"
+ hashfunc = getattr(hashlib, pwfields.hashfunc)
+
pwfields.salt = os.urandom(32)
# https://forums.lastpass.com/viewtopic.php?t=84104
pwfields.iterations = 100000
- pwfields.keylength = 24
+ pwfields.keylength = 32
pwfields.created_ts = datetime.datetime.utcnow().isoformat()
# One more check on password length
@@ -111,16 +128,102 @@ class RegisterUser(object):
print("pwfields:", vars(pwfields))
print("locals:", locals())
+
+ # 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, algorithm)
- print("Creating password took:", time.time() - start_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)
+
self.user.password = json.dumps(pwfields.__dict__,
sort_keys=True,
# See http://stackoverflow.com/a/12312896
encoding="latin-1"
)
+ def send_email_verification(self):
+ verification_code = str(uuid.uuid4())
+ key = "verification_code:" + verification_code
+
+ data = json.dumps(dict(the_id=self.new_user.the_id,
+ timestamp=datetime.datetime.utcnow().isoformat())
+ )
+
+ Redis.set(key, data)
+ two_days = 60 * 60 * 24 * 2
+ Redis.expire(key, two_days)
+ to = self.user.email_address
+ subject = "GeneNetwork email verification"
+ body = render_template("email/verification.txt",
+ verification_code = verification_code)
+ send_email(to, subject, body)
+
+
+def verify_email(request):
+ print("in verify_email request.url is:", request.url)
+ verify_url_hmac(request.url)
+ verification_code = request.args['code']
+ data = Redis.get("verification_code:" + verification_code)
+ data = json.loads(data)
+ print("data is:", data)
+
+
+
+################################# Sign and unsign #####################################
+def url_for_hmac(endpoint, **values):
+ """Like url_for but adds an hmac at the end to insure the url hasn't been tampered with"""
+
+ url = url_for(endpoint, **values)
+
+ hm = actual_hmac_creation(url)
+ if '?' in url:
+ combiner = "&"
+ else:
+ combiner = "?"
+ return url + combiner + "hm=" + hm
+
+def verify_url_hmac(url):
+ """Pass in a url that was created with url_hmac and this assures it hasn't been tampered with"""
+ print("url passed in to verify is:", url)
+ # Verify parts are correct at the end - we expect to see &hm= or ?hm= followed by an hmac
+ assert url[-23:-20] == "hm=", "Unexpected url (stage 1)"
+ assert url[-24] in ["?", "&"], "Unexpected url (stage 2)"
+ hmac = url[-20:]
+ url = url[:-24] # Url without any of the hmac stuff
+
+ #print("before urlsplit, url is:", url)
+ #url = divide_up_url(url)[1]
+ #print("after urlsplit, url is:", url)
+
+ hm = actual_hmac_creation(url)
+
+ assert hm == hmac, "Unexpected url (stage 3)"
+
+def actual_hmac_creation(url):
+ """Helper function to create the actual hmac"""
+
+ secret = app.config['SECRET_HMAC_CODE']
+
+ hmaced = hmac.new(secret, url, hashlib.sha1)
+ hm = hmaced.hexdigest()
+ # "Conventional wisdom is that you don't lose much in terms of security if you throw away up to half of the output."
+ # http://www.w3.org/QA/2009/07/hmac_truncation_in_xml_signatu.html
+ hm = hm[:20]
+ return hm
+
+app.jinja_env.globals.update(url_for_hmac=url_for_hmac)
+
+#######################################################################################
+
+def send_email(to, subject, body):
+ msg = json.dumps(dict(From="no-reply@genenetwork.org",
+ To=to,
+ Subject=subject,
+ Body=body))
+ Redis.rpush("mail_queue", msg)
+
#def combined_salt(user_salt):
# """Combine the master salt with the user salt...we use two seperate salts so that if the database is compromised, the
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index 82d28eab..b552e160 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -292,6 +292,11 @@ def manage_groups():
template_vars = user_manager.GroupsManager(request.args)
return render_template("admin/group_manager.html", **template_vars.__dict__)
+@app.route("/manage/verify")
+def verify():
+ user_manager.verify_email(request)
+ return "foo"
+
@app.route("/n/register", methods=('GET', 'POST'))
def new_register():