diff options
-rw-r--r-- | wqflask/secure_server.py | 40 | ||||
-rw-r--r-- | wqflask/wqflask/send_mail.py | 39 | ||||
-rw-r--r-- | wqflask/wqflask/templates/email/forgot_password.txt | 5 | ||||
-rw-r--r-- | wqflask/wqflask/templates/email/verification.txt | 3 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/forgot_password.html | 65 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/forgot_password_step2.html | 33 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/login_user.html | 55 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/password_reset.html | 80 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/register_user.html | 56 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/registered.html | 11 | ||||
-rw-r--r-- | wqflask/wqflask/user_manager.py | 342 | ||||
-rw-r--r-- | wqflask/wqflask/views.py | 64 |
12 files changed, 546 insertions, 247 deletions
diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py index a77abf7e..d5f1a291 100644 --- a/wqflask/secure_server.py +++ b/wqflask/secure_server.py @@ -1,12 +1,18 @@ from __future__ import absolute_import, division, print_function +import time +import sys + from wqflask import app from flask import Flask, render_template +import redis +Redis = redis.StrictRedis() + # Setup mail -from flask.ext.mail import Mail -mail = Mail(app) +#from flask.ext.mail import Mail +#mail = Mail(app) from wqflask.model import * @@ -33,8 +39,38 @@ app.wsgi_app = ProxyFix(app.wsgi_app) #print("app.config is:", app.config) + + +def check_send_mail_running(): + """Ensure send_mail.py is running before we start the site + + It would be really easy to accidentally run the site + without our mail program running + This will make sure our mail program is running...or at least recently run... + + """ + error_msg = "Make sure your are running send_mail.py" + send_mail_ping = Redis.get("send_mail:ping") + print("send_mail_ping is:", send_mail_ping) + if not send_mail_ping: + sys.exit(error_msg) + + last_ping = time.time() - float(send_mail_ping) + if not (0 < last_ping < 100): + sys.exit(error_msg) + + + print("send_mail.py seems to be running...") + + if __name__ == '__main__': #create_user() + + + + check_send_mail_running() + + app.run(host='0.0.0.0', port=app.config['SERVER_PORT'], use_debugger=False, diff --git a/wqflask/wqflask/send_mail.py b/wqflask/wqflask/send_mail.py index be51ad0d..bf5d0dd8 100644 --- a/wqflask/wqflask/send_mail.py +++ b/wqflask/wqflask/send_mail.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, division, print_function import datetime +import time import simplejson as json @@ -12,34 +13,40 @@ 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) - + print("I'm alive!") + + # Set something so we know it's running (or at least been running recently) + Redis.setex("send_mail:ping", 300, time.time()) + + msg = Redis.blpop("mail_queue", 30) + + if msg: + # Queue name is the first element, we want the second, which is the actual message + msg = msg[1] + + print("\n\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 + main() diff --git a/wqflask/wqflask/templates/email/forgot_password.txt b/wqflask/wqflask/templates/email/forgot_password.txt new file mode 100644 index 00000000..e7d1389b --- /dev/null +++ b/wqflask/wqflask/templates/email/forgot_password.txt @@ -0,0 +1,5 @@ +Sorry to hear you lost your GeneNetwork password. + +To reset your password please click the following link, or cut and paste it into your browser window: + +{{ url_for_hmac("password_reset", code = verification_code, _external=True )}} diff --git a/wqflask/wqflask/templates/email/verification.txt b/wqflask/wqflask/templates/email/verification.txt index 29229c68..76149a3a 100644 --- a/wqflask/wqflask/templates/email/verification.txt +++ b/wqflask/wqflask/templates/email/verification.txt @@ -4,5 +4,4 @@ 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 )}} - +{{ url_for_hmac("verify_email", code = verification_code, _external=True )}} diff --git a/wqflask/wqflask/templates/new_security/forgot_password.html b/wqflask/wqflask/templates/new_security/forgot_password.html new file mode 100644 index 00000000..39e51f96 --- /dev/null +++ b/wqflask/wqflask/templates/new_security/forgot_password.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Forgot Password{% endblock %} +{% block content %} + + {{ header("Forgot Password", "Easily reset your password.") }} + + <div class="container"> + <div class="page-header"> + <h1>Password Reset</h1> + </div> + + + <div class="security_box"> + + <h4>Enter your email address</h4> + + <h5>And we'll send you a link to reset your password</h5> + + + + <form class="form-horizontal" action="/n/forgot_password_submit" + 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"> + <div class="controls"> + <input id="next" name="next" type="hidden" value=""> + + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Send link"> + </div> + </div> + + </fieldset> + + <br /> + + <div class="well"> + <h5>Has your email address changed?</h5> + + If you no longer use the email address connected to your account, you can contact us for assistance. + + </div> + + </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/forgot_password_step2.html b/wqflask/wqflask/templates/new_security/forgot_password_step2.html new file mode 100644 index 00000000..1295e589 --- /dev/null +++ b/wqflask/wqflask/templates/new_security/forgot_password_step2.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + <header class="jumbotron subhead" id="overview"> + <div class="container"> + <h1>Password Reset</h1> + <p class="lead"> + Check your email. + </p> + </div> + </header> + + <div class="container"> + <div class="page-header"> + <h3>One last step</h3> + </div> + + <p>You will receive an email with the subject <strong>"{{ subject }}"</strong>.</p> + + <p>You must click the link in the email to reset the password.</p> + + <p>If you don't see the email, check your spam folder.</p> + </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" %} + <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html index f232ccbc..4e308c75 100644 --- a/wqflask/wqflask/templates/new_security/login_user.html +++ b/wqflask/wqflask/templates/new_security/login_user.html @@ -2,82 +2,81 @@ {% block title %}Register{% endblock %} {% block content %} - {{ header("Login", "Gain access to GeneNetwork.") }} + {{ header("Sign in", "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/> + <a href="/n/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 %} +{% 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/password_reset.html b/wqflask/wqflask/templates/new_security/password_reset.html new file mode 100644 index 00000000..cda1e477 --- /dev/null +++ b/wqflask/wqflask/templates/new_security/password_reset.html @@ -0,0 +1,80 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + {{ header("Password Reset", "Create a new password.") }} + + + <div class="container"> + <div class="page-header"> + <h1>Password reset</h1> + </div> + + <div class="security_box"> + + + <h4>Enter your new password</h4> + + + {% if errors %} + <div class="alert alert-error"> + <strong>Please note:</strong> + <ul> + {% for error in errors %} + <li>{{error}}</li> + {% endfor %} + </ul> + </div> + {% endif %} + + <form class="form-horizontal" action="/n/password_reset_step2" data-validate="parsley" + method="POST" name="login_user_form"> + + <fieldset> + + <input class="hidden" name="user_encode" value="{{ user_encode }}"> + + + <div class="control-group"> + <label class="control-label" for="password">Password</label> + <div class="controls"> + <input id="password" name="password" type="password" value="" + data-trigger="change" data-required="true" data-minlength="6"> + </div> + </div> + + <div class="control-group" style="display: none" id="password_alert"> + <div class="controls""> + <span id="password_strength" class="alert"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="password_confirm">Confirm Password</label> + <div class="controls"> + <input id="password" name="password_confirm" type="password" value="" + data-trigger="change" data-required="true" data-equalto="#password"> + </div> + </div> + + <div class="control-group"> + <div class="controls""> + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Reset password"> + </div> + </div> + + </fieldset> + + </form> + </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" %} + <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/wqflask/wqflask/templates/new_security/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html index 2a02e7ca..998d2a7b 100644 --- a/wqflask/wqflask/templates/new_security/register_user.html +++ b/wqflask/wqflask/templates/new_security/register_user.html @@ -1,36 +1,31 @@ {% extends "base.html" %} {% block title %}Register{% endblock %} {% block content %} - <header class="jumbotron subhead" id="overview"> - <div class="container"> - <h1>Register</h1> - <p class="lead"> - It's easy and fast to make an account. - </p> - </div> - </header> + + {{ header("Register", "It's fast and easy to make an account.") }} + <div class="container"> <div class="page-header"> <h1>Registration</h1> </div> - + <div class="security_box"> <h4>Already have an account?</h4> - - + + <a href="/n/login" class="btn btn-info modalize">Sign in using existing account</a> - - + + <hr /> - + <h4>Don't have an account?</h4> - + <h5>Register here</h5> - + {% if errors %} - <div class="alert alert-error"> + <div class="alert alert-error"> <strong>Please note:</strong> <ul> {% for error in errors %} @@ -39,11 +34,11 @@ </ul> </div> {% endif %} - + <form class="form-horizontal" action="/n/register" data-validate="parsley" method="POST" name="login_user_form"> <fieldset> - + <div class="control-group"> <label class="control-label" for="email_address">Email Address</label> <div class="controls"> @@ -51,7 +46,7 @@ data-trigger="change" data-required="true" data-type="email" data-maxlength="50"> </div> </div> - + <div class="control-group"> <label class="control-label" for="full_name">Full Name</label> <div class="controls"> @@ -59,28 +54,28 @@ data-trigger="change" data-required="true" data-minlength="5" data-maxlength="50"> </div> </div> - + <div class="control-group"> <label class="control-label" for="organization">Organization</label> <div class="controls"> <input id="organization" name="organization" type="text" value="{{values.organization}}" data-minlength="3" data-maxlength="50"> </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="" data-trigger="change" data-required="true" data-minlength="6"> </div> - </div> - + </div> + <div class="control-group" style="display: none" id="password_alert"> <div class="controls""> <span id="password_strength" class="alert"></span> </div> </div> - + <div class="control-group"> <label class="control-label" for="password_confirm">Confirm Password</label> <div class="controls"> @@ -88,26 +83,25 @@ data-trigger="change" data-required="true" data-equalto="#password"> </div> </div> - + <div class="control-group"> <div class="controls""> <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Create account"> </div> </div> - + </fieldset> - + </form> </div> </div> {% endblock %} -{% block js %} +{% block js %} <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>--> - + {% include "new_security/_scripts.html" %} <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script> <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> {% endblock %} - diff --git a/wqflask/wqflask/templates/new_security/registered.html b/wqflask/wqflask/templates/new_security/registered.html index 391a6044..49dc961f 100644 --- a/wqflask/wqflask/templates/new_security/registered.html +++ b/wqflask/wqflask/templates/new_security/registered.html @@ -15,20 +15,19 @@ <h3>One last step</h3> </div> - <p>You will receive an email with the subject <strong>"GeneNetwork email verification"</strong>.</p> - + <p>You will receive an email with the subject <strong>"{{ subject }}"</strong>.</p> + <p>You must click the link in the email to complete registration.</p> - + <p>If you don't see the email, check your spam folder.</p> </div> {% endblock %} -{% block js %} +{% block js %} <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>--> - + {% include "new_security/_scripts.html" %} <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script> <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> {% endblock %} - diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py index a2dff7f2..766f49df 100644 --- a/wqflask/wqflask/user_manager.py +++ b/wqflask/wqflask/user_manager.py @@ -17,10 +17,15 @@ import hashlib import hmac import base64 +import urlparse + import simplejson as json -from redis import StrictRedis -Redis = StrictRedis() +from sqlalchemy import orm + +#from redis import StrictRedis +import redis +Redis = redis.StrictRedis() from flask import (Flask, g, render_template, url_for, request, make_response, @@ -53,7 +58,7 @@ def timestamp(): class UserSession(object): cookie_name = 'session_id' - + def __init__(self): cookie = request.cookies.get(self.cookie_name) if not cookie: @@ -70,13 +75,13 @@ class UserSession(object): self.record = Redis.hgetall(self.redis_key) print("record is:", self.record) self.logged_in = True - - + + def delete_session(self): # And more importantly delete the redis record Redis.delete(self.cookie_name) print("At end of delete_session") - + @app.before_request def before_request(): g.user_session = UserSession() @@ -105,22 +110,22 @@ class UserManager(object): print(" ID:", dataset.id) print(" Confidential:", dataset.check_confidentiality()) #print(" ---> self.datasets:", self.datasets) - + class RegisterUser(object): def __init__(self, kw): self.thank_you_mode = False self.errors = [] self.user = Bunch() - + self.user.email_address = kw.get('email_address', '').strip() if not (5 <= len(self.user.email_address) <= 50): self.errors.append('Email Address needs to be between 5 and 50 characters.') - + self.user.full_name = kw.get('full_name', '').strip() if not (5 <= len(self.user.full_name) <= 50): self.errors.append('Full Name needs to be between 5 and 50 characters.') - + self.user.organization = kw.get('organization', '').strip() if self.user.organization and not (5 <= len(self.user.organization) <= 50): self.errors.append('Organization needs to be empty or between 5 and 50 characters.') @@ -128,81 +133,94 @@ class RegisterUser(object): password = str(kw.get('password', '')) if not (6 <= len(password)): self.errors.append('Password needs to be at least 6 characters.') - + if kw.get('password_confirm') != password: self.errors.append("Passwords don't match.") - + if self.errors: return - + print("No errors!") - - self.set_password(password) - + + set_password(password, user) + self.user.registration_info = json.dumps(basic_info(), sort_keys=True) - + self.new_user = model.User(**self.user.__dict__) db_session.add(self.new_user) db_session.commit() - + print("Adding verification email to queue") - self.send_email_verification() + #self.send_email_verification() + VerificationEmail(self.new_user) print("Added verification email to queue") - + self.thank_you_mode = True - - - def set_password(self, password): - pwfields = Bunch() - - pwfields.algorithm = "pbkdf2" - pwfields.hashfunc = "sha256" - #hashfunc = getattr(hashlib, pwfields.hashfunc) - - # 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 - pwfields.keylength = 32 - - pwfields.created_ts = timestamp() - # One more check on password length - assert len(password) >= 6, "Password shouldn't be so short here" - - print("pwfields:", vars(pwfields)) - print("locals:", locals()) - - enc_password = Password(password, - pwfields.salt, - pwfields.iterations, - pwfields.keylength, - pwfields.hashfunc) - - pwfields.password = enc_password.password - pwfields.encrypt_time = enc_password.encrypt_time - - self.user.password = json.dumps(pwfields.__dict__, - sort_keys=True, - ) - - def send_email_verification(self): + + +def set_password(password, user): + pwfields = Bunch() + + pwfields.algorithm = "pbkdf2" + pwfields.hashfunc = "sha256" + #hashfunc = getattr(hashlib, pwfields.hashfunc) + + # 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 + pwfields.keylength = 32 + + pwfields.created_ts = timestamp() + # One more check on password length + assert len(password) >= 6, "Password shouldn't be so short here" + + print("pwfields:", vars(pwfields)) + print("locals:", locals()) + + enc_password = Password(password, + pwfields.salt, + pwfields.iterations, + pwfields.keylength, + pwfields.hashfunc) + + pwfields.password = enc_password.password + pwfields.encrypt_time = enc_password.encrypt_time + + user.password = json.dumps(pwfields.__dict__, + sort_keys=True, + ) + + +class VerificationEmail(object): + template_name = "email/verification.txt" + key_prefix = "verification_code" + subject = "GeneNetwork email verification" + + def __init__(self, user): verification_code = str(uuid.uuid4()) - key = "verification_code:" + verification_code - - data = json.dumps(dict(id=self.new_user.id, + key = self.key_prefix + ":" + verification_code + + data = json.dumps(dict(id=user.id, timestamp=timestamp()) ) - + 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", + Redis.expire(key, two_days) + to = user.email_address + subject = self.subject + body = render_template(self.template_name, verification_code = verification_code) send_email(to, subject, body) +class ForgotPasswordEmail(VerificationEmail): + template_name = "email/forgot_password.txt" + key_prefix = "forgot_password_code" + subject = "GeneNetwork password reset" + + class Password(object): def __init__(self, unencrypted_password, salt, iterations, keylength, hashfunc): hashfunc = getattr(hashlib, hashfunc) @@ -215,24 +233,80 @@ class Password(object): 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(), ip_address = request.remote_addr, user_agent = request.headers.get('User-Agent')) +@app.route("/manage/verify_email") def verify_email(): - 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) - user = model.User.query.get(data['id']) + user = DecodeUser(VerificationEmail.key_prefix).user user.confirmed = json.dumps(basic_info(), sort_keys=True) db_session.commit() - + +@app.route("/n/password_reset") +def password_reset(): + print("in password_reset request.url is:", request.url) + + # We do this mainly just to assert that it's in proper form for displaying next page + # Really not necessary but doesn't hurt + user_encode = DecodeUser(ForgotPasswordEmail.key_prefix).reencode_standalone() + + return render_template("new_security/password_reset.html", user_encode=user_encode) + +@app.route("/n/password_reset_step2", methods=('POST',)) +def password_reset_step2(): + print("in password_reset request.url is:", request.url) + + errors = [] + + user_encode = request.form['user_encode'] + verification_code, separator, hmac = user_encode.partition(':') + + hmac_verified = actual_hmac_creation(verification_code) + print("locals are:", locals()) + + + assert hmac == hmac_verified, "Someone has been naughty" + + user = DecodeUser.actual_get_user(ForgotPasswordEmail.key_prefix, verification_code) + print("user is:", user) + + password = request.form['password'] + + set_password(password, user) + db_session.commit() + + flash("Password changed successfully. You can now sign in.", "alert-info") + response = make_response(redirect(url_for('login'))) + + return response + +class DecodeUser(object): + + def __init__(self, code_prefix): + verify_url_hmac(request.url) + + #params = urlparse.parse_qs(url) + + self.verification_code = request.args['code'] + self.user = self.actual_get_user(code_prefix, self.verification_code) + + def reencode_standalone(self): + hmac = actual_hmac_creation(self.verification_code) + return self.verification_code + ":" + hmac + + @staticmethod + def actual_get_user(code_prefix, verification_code): + data = Redis.get(code_prefix + ":" + verification_code) + print("in get_coded_user, data is:", data) + data = json.loads(data) + print("data is:", data) + return model.User.query.get(data['id']) + +@app.route("/n/login", methods=('GET', 'POST')) def login(): params = request.form if request.form else request.args print("in login params are:", params) @@ -250,10 +324,10 @@ def login(): 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()) @@ -261,17 +335,17 @@ def login(): session_id_signature = actual_hmac_creation(login_rec.session_id) session_id_signed = login_rec.session_id + ":" + session_id_signature print("session_id_signed:", session_id_signed) - + session = dict(login_time = time.time(), user_id = user.id, user_email_address = user.email_address) - - flash("Thank you for logging in.", "alert-success") - + + flash("Thank you for logging in {}.".format(user.full_name), "alert-success") + key = "session_id:" + login_rec.session_id print("Key when signing:", key) Redis.hmset(key, session) - + response = make_response(redirect(url_for('index_page'))) response.set_cookie(UserSession.cookie_name, session_id_signed) else: @@ -281,8 +355,8 @@ def login(): db_session.add(login_rec) db_session.commit() return response - -@app.route("/n/logout") + +@app.route("/n/logout") def logout(): print("Logging out...") UserSession().delete_session() @@ -291,15 +365,89 @@ def logout(): # Delete the cookie response.set_cookie(UserSession.cookie_name, '', expires=0) return response - - + + +@app.route("/n/forgot_password") +def forgot_password(): + return render_template("new_security/forgot_password.html") + +@app.route("/n/forgot_password_submit", methods=('POST',)) +def forgot_password_submit(): + params = request.form + email_address = params['email_address'] + try: + user = model.User.query.filter_by(email_address=email_address).one() + except orm.exc.NoResultFound: + flash("Couldn't find a user associated with the email address {}. Sorry.".format( + email_address)) + return redirect(url_for("login")) + ForgotPasswordEmail(user) + return render_template("new_security/forgot_password_step2.html", + subject=ForgotPasswordEmail.subject) + + + +@app.route("/manage/users") +def manage_users(): + template_vars = UsersManager() + return render_template("admin/user_manager.html", **template_vars.__dict__) + +@app.route("/manage/user") +def manage_user(): + template_vars = UserManager(request.args) + return render_template("admin/ind_user_manager.html", **template_vars.__dict__) + +@app.route("/manage/groups") +def manage_groups(): + template_vars = GroupsManager(request.args) + return render_template("admin/group_manager.html", **template_vars.__dict__) + + +@app.route("/n/register", methods=('GET', 'POST')) +def register(): + params = None + errors = None + + #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 = RegisterUser(params) + errors = result.errors + + if result.thank_you_mode: + assert not errors, "Errors while in thank you mode? That seems wrong..." + return render_template("new_security/registered.html", + subject=VerificationEmail.subject) + + return render_template("new_security/register_user.html", values=params, errors=errors) + + + + +#@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") + + + ################################# 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 = "&" @@ -323,10 +471,10 @@ def verify_url_hmac(url): hm = actual_hmac_creation(url) assert hm == hmac, "Unexpected url (stage 3)" - + def actual_hmac_creation(stringy): """Helper function to create the actual hmac""" - + secret = app.config['SECRET_HMAC_CODE'] hmaced = hmac.new(secret, stringy, hashlib.sha1) @@ -339,14 +487,14 @@ def actual_hmac_creation(stringy): 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 @@ -358,7 +506,7 @@ def send_email(to, subject, body): # for x, y in user_salt, secret_salt: # combined = combined + x + y # return combined - + class GroupsManager(object): @@ -374,9 +522,5 @@ class RolesManager(object): #class Password(object): # """To generate a master password: dd if=/dev/urandom bs=32 count=1 > master_salt""" -# -# master_salt = - - - - +# +# master_salt = diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index deccf459..a060ba3c 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -51,7 +51,7 @@ from wqflask import user_manager @app.before_request def connect_db(): g.db = sqlalchemy.create_engine(app.config['DB_URI']) - + #@app.before_request #def trace_it(): # from wqflask import tracer @@ -277,68 +277,6 @@ def get_temp_data(): ################################################################################################### -@app.route("/manage/users") -def manage_users(): - template_vars = user_manager.UsersManager() - return render_template("admin/user_manager.html", **template_vars.__dict__) - -@app.route("/manage/user") -def manage_user(): - template_vars = user_manager.UserManager(request.args) - return render_template("admin/ind_user_manager.html", **template_vars.__dict__) - -@app.route("/manage/groups") -def manage_groups(): - template_vars = user_manager.GroupsManager(request.args) - return render_template("admin/group_manager.html", **template_vars.__dict__) - - -@app.route("/n/register", methods=('GET', 'POST')) -def register(): - params = None - errors = None - - #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) - errors = result.errors - - if result.thank_you_mode: - assert not errors, "Errors while in thank you mode? That seems wrong..." - return render_template("new_security/registered.html") - - return render_template("new_security/register_user.html", values=params, errors=errors) - -#@app.route("/n/register_submit", methods=('POST',)) -#def register_submit(): -# print("request.args are: ", request.args) -# result = user_manager.RegisterUser(request.form) -# if result.errors: -# print("Redirecting") -# # 307 preserves the post on the redirect (maybe) -# errors = result.errors -# #errors = json.dumps(errors) -# print("request.args are: ", request.args) -# return render_template("new_security/register_user.html", errors=errors, values=request.form) -# #return redirect(url_for('new_register', errors=errors), code=307) - - -@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") - ########################################################################## |