aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLei Yan2013-10-11 14:49:54 -0500
committerLei Yan2013-10-11 14:49:54 -0500
commit79aed6879f138bb083af91f50cd4827789683062 (patch)
tree722be02d7f7ecbc7094a7519f59f6802e123fb97
parent8a09358e98dbf88deb101d13107a40bac371de5c (diff)
parent38ae30a5fb46753a361e1c7454871430d7097c3b (diff)
downloadgenenetwork2-79aed6879f138bb083af91f50cd4827789683062.tar.gz
Merge /home/sam/gene
-rw-r--r--wqflask/flask_security/__init__.py4
-rw-r--r--wqflask/flask_security/core.py1
-rw-r--r--wqflask/flask_security/datastore.py1
-rw-r--r--wqflask/flask_security/forms.py9
-rw-r--r--wqflask/flask_security/registerable.py1
-rw-r--r--wqflask/other_config/nginx_conf/sam.conf42
-rw-r--r--wqflask/secure_server.py8
-rwxr-xr-xwqflask/utility/__init__.py23
-rw-r--r--wqflask/wqflask/database.py26
-rw-r--r--wqflask/wqflask/model.py110
-rw-r--r--wqflask/wqflask/pbkdf2.py140
-rw-r--r--wqflask/wqflask/send_mail.py45
-rw-r--r--wqflask/wqflask/static/new/css/parsley.css20
-rw-r--r--wqflask/wqflask/static/new/javascript/password_strength.coffee33
-rw-r--r--wqflask/wqflask/static/new/javascript/password_strength.js42
-rw-r--r--wqflask/wqflask/static/new/js_external/parsley.min.js35
-rw-r--r--wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js1
-rw-r--r--wqflask/wqflask/templates/admin/ind_user_manager.html10
-rw-r--r--wqflask/wqflask/templates/admin/user_manager.html14
-rw-r--r--wqflask/wqflask/templates/base.html43
-rw-r--r--wqflask/wqflask/templates/email/verification.txt8
-rw-r--r--wqflask/wqflask/templates/index_page.html7
-rw-r--r--wqflask/wqflask/templates/new_security/_scripts.html1
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html83
-rw-r--r--wqflask/wqflask/templates/new_security/register_user.html113
-rw-r--r--wqflask/wqflask/templates/new_security/registered.html34
-rw-r--r--wqflask/wqflask/templates/new_security/thank_you.html32
-rw-r--r--wqflask/wqflask/templates/new_security/verified.html32
-rw-r--r--wqflask/wqflask/templates/security/register_user.html16
-rw-r--r--wqflask/wqflask/tracer.py41
-rw-r--r--wqflask/wqflask/user_manager.py336
-rw-r--r--wqflask/wqflask/views.py65
32 files changed, 1314 insertions, 62 deletions
diff --git a/wqflask/flask_security/__init__.py b/wqflask/flask_security/__init__.py
index 297033c9..81e6c89e 100644
--- a/wqflask/flask_security/__init__.py
+++ b/wqflask/flask_security/__init__.py
@@ -12,8 +12,6 @@
__version__ = '1.6.0'
-print "using internal flask security"
-
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
from .decorators import auth_token_required, http_auth_required, \
@@ -23,3 +21,5 @@ from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \
from .signals import confirm_instructions_sent, password_reset, \
reset_password_instructions_sent, user_confirmed, user_registered
from .utils import login_user, logout_user, url_for_security
+
+print "Using our own flask.ext.security" \ No newline at end of file
diff --git a/wqflask/flask_security/core.py b/wqflask/flask_security/core.py
index d794fad5..0f3a231f 100644
--- a/wqflask/flask_security/core.py
+++ b/wqflask/flask_security/core.py
@@ -207,6 +207,7 @@ def _get_serializer(app, name):
def _get_state(app, datastore, **kwargs):
for key, value in get_config(app).items():
+ print "in _get_state [{}]: {}".format(key, value)
kwargs[key.lower()] = value
kwargs.update(dict(
diff --git a/wqflask/flask_security/datastore.py b/wqflask/flask_security/datastore.py
index f8c7218d..634399d9 100644
--- a/wqflask/flask_security/datastore.py
+++ b/wqflask/flask_security/datastore.py
@@ -157,6 +157,7 @@ class UserDatastore(object):
"""Creates and returns a new user from the given parameters."""
user = self.user_model(**self._prepare_create_user_args(**kwargs))
+ print "in abstraced create_user, user is:", user
return self.put(user)
def delete_user(self, user):
diff --git a/wqflask/flask_security/forms.py b/wqflask/flask_security/forms.py
index e64e1502..54677e77 100644
--- a/wqflask/flask_security/forms.py
+++ b/wqflask/flask_security/forms.py
@@ -89,6 +89,12 @@ def valid_user_email(form, field):
class Form(BaseForm):
def __init__(self, *args, **kwargs):
+ #print "importing tracer"
+ #from wqflask import tracer
+ #tracer.turn_on()
+ #print "imported tracer"
+ print "in Form, args:", args
+ print "in Form, kwargs:", kwargs
if current_app.testing:
self.TIME_LIMIT = None
super(Form, self).__init__(*args, **kwargs)
@@ -148,10 +154,13 @@ class RegisterFormMixin():
def to_dict(form):
def is_field_and_user_attr(member):
+ print "in ifaua:", member
return isinstance(member, Field) and \
hasattr(_datastore.user_model, member.name)
+ print("green:", vars(form))
fields = inspect.getmembers(form, is_field_and_user_attr)
+ print("fields:" ,vars(form))
return dict((key, value.data) for key, value in fields)
diff --git a/wqflask/flask_security/registerable.py b/wqflask/flask_security/registerable.py
index 4e9f357d..4606c7c6 100644
--- a/wqflask/flask_security/registerable.py
+++ b/wqflask/flask_security/registerable.py
@@ -24,6 +24,7 @@ _datastore = LocalProxy(lambda: _security.datastore)
def register_user(**kwargs):
+ print "in register_user kwargs:", kwargs
confirmation_link, token = None, None
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)
diff --git a/wqflask/other_config/nginx_conf/sam.conf b/wqflask/other_config/nginx_conf/sam.conf
new file mode 100644
index 00000000..3cc443a8
--- /dev/null
+++ b/wqflask/other_config/nginx_conf/sam.conf
@@ -0,0 +1,42 @@
+server {
+ # Modeled after http://flask.pocoo.org/docs/deploying/wsgi-standalone/
+ listen 80;
+
+ server_name sam.genenetwork.org;
+
+ access_log /var/log/nginx/access.log;
+ error_log /var/log/nginx/error.log;
+
+ location ^~ /css/ {
+ root /gene/wqflask/wqflask/static/;
+ }
+
+ location ^~ /javascript/ {
+ root /gene/wqflask/wqflask/static/;
+ }
+
+# location ^~ /image/ {
+# root /gene/wqflask/wqflask/static/;
+# }
+
+ location ^~ /images/ {
+ root /gene/wqflask/wqflask/static/;
+ }
+
+ ### New - added by Sam
+ #location ^~ /static/ {
+ # root /gene/wqflask/wqflask/static/;
+ #}
+
+ location / {
+ proxy_pass http://127.0.0.1:5555/;
+ proxy_redirect off;
+
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ proxy_read_timeout 40m;
+ }
+}
+
diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py
index 2abfdb05..a77abf7e 100644
--- a/wqflask/secure_server.py
+++ b/wqflask/secure_server.py
@@ -1,4 +1,4 @@
-from __future__ import print_function, division, absolute_import
+from __future__ import absolute_import, division, print_function
from wqflask import app
@@ -25,6 +25,12 @@ app.logger.addHandler(file_handler)
import logging_tree
logging_tree.printout()
+#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)
if __name__ == '__main__':
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/database.py b/wqflask/wqflask/database.py
new file mode 100644
index 00000000..e55f06a7
--- /dev/null
+++ b/wqflask/wqflask/database.py
@@ -0,0 +1,26 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import scoped_session, sessionmaker
+from sqlalchemy.ext.declarative import declarative_base
+
+from wqflask import app
+
+#engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
+engine = create_engine(app.config['DB_URI'], convert_unicode=True)
+
+db_session = scoped_session(sessionmaker(autocommit=False,
+ autoflush=False,
+ bind=engine))
+Base = declarative_base()
+Base.query = db_session.query_property()
+
+def init_db():
+ # 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 yourapplication.models
+ import wqflask.model
+ print("Creating all..")
+ Base.metadata.create_all(bind=engine)
+ print("Done creating all...")
+
+init_db() \ No newline at end of file
diff --git a/wqflask/wqflask/model.py b/wqflask/wqflask/model.py
index 5beba9ff..5c514bde 100644
--- a/wqflask/wqflask/model.py
+++ b/wqflask/wqflask/model.py
@@ -1,12 +1,25 @@
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
+#from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
+
+#from flask_security.forms import TextField
+#from flask_security.forms import RegisterForm
from wqflask import app
+from sqlalchemy import Column, Integer, String, Table, ForeignKey, Unicode, Boolean, DateTime, Text
+from sqlalchemy.orm import relationship, backref
+
+from wqflask.database import Base, init_db
+
# Create database connection object
-db = SQLAlchemy(app)
+#db = SQLAlchemy(app)
+
# Is this right? -Sam
#from sqlalchemy.ext.declarative import declarative_base
@@ -23,45 +36,76 @@ db = SQLAlchemy(app)
# """
# print("in get cls is:", cls)
# print(" key is {} : {}".format(type(key), key))
-# query = db.Model.query(cls)
+# query = Model.query(cls)
# print("query is: ", query)
# record = query.get(key)
# return record
#
#
-#print("db.Model is:", vars(db.Model))
-#db.Model.get = get
+#print("Model is:", vars(Model))
+#Model.get = get
# Define models
-roles_users = db.Table('roles_users',
- db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
- db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
-
-class Role(db.Model, RoleMixin):
- id = db.Column(db.Integer(), primary_key=True)
- name = db.Column(db.String(80), unique=True)
- description = db.Column(db.String(255))
-
-class User(db.Model, UserMixin):
- id = db.Column(db.Integer(), primary_key=True)
- email = db.Column(db.String(255), unique=True)
- password = db.Column(db.String(255))
- active = db.Column(db.Boolean())
- confirmed_at = db.Column(db.DateTime())
-
- last_login_at = db.Column(db.DateTime())
- current_login_at = db.Column(db.DateTime())
- last_login_ip = db.Column(db.String(39))
- current_login_ip = db.Column(db.String(39))
- login_count = db.Column(db.Integer())
-
- roles = db.relationship('Role', secondary=roles_users,
- backref=db.backref('users', lazy='dynamic'))
+#roles_users = Table('roles_users',
+# Column('user_id', Integer(), ForeignKey('user.the_id')),
+# Column('role_id', Integer(), ForeignKey('role.the_id')))
+
+class Role(Base):
+ __tablename__ = "role"
+ id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4()))
+ name = Column(Unicode(80), unique=True, nullable=False)
+ description = Column(Unicode(255))
+
+class User(Base):
+ __tablename__ = "user"
+ id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4()))
+ email_address = Column(Unicode(50), unique=True, nullable=False)
+
+ # Todo: Turn on strict mode for Mysql
+ password = Column(Text, nullable=False)
+
+ full_name = Column(Unicode(50))
+ organization = Column(Unicode(50))
+
+ active = Column(Boolean(), nullable=False, default=True)
+ registration_info = Column(Text) # json detailing when they were registered, etc.
+
+ confirmed = Column(Text) # json detailing when they confirmed, etc.
+
+ #last_login_at = Column(DateTime())
+ #current_login_at = Column(DateTime())
+ #last_login_ip = Column(Unicode(39))
+ #current_login_ip = Column(Unicode(39))
+ #login_count = Column(Integer())
+
+ #roles = relationship('Role', secondary=roles_users,
+ # backref=backref('users', lazy='dynamic'))
+
+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(), 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)
-security = Security(app, user_datastore)
+#user_datastore = SQLAlchemyUserDatastore(db, User, Role)
+
+#class ExtendedRegisterForm(RegisterForm):
+# name = TextField('name')
+# #print("name is:", name['_name'], vars(name))
+# organization = TextField('organization')
+#
+#security = Security(app, user_datastore, register_form=ExtendedRegisterForm)
+
+
+#user_datastore.create_role(name="Genentech", description="Genentech Beta Project(testing)")
-db.metadata.create_all(db.engine)
-user_datastore.create_role(name="Genentech", description="Genentech Beta Project(testing)")
diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py
new file mode 100644
index 00000000..f7f61a09
--- /dev/null
+++ b/wqflask/wqflask/pbkdf2.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+"""
+ pbkdf2
+ ~~~~~~
+
+ This module implements pbkdf2 for Python. It also has some basic
+ tests that ensure that it works. The implementation is straightforward
+ and uses stdlib only stuff and can be easily be copy/pasted into
+ your favourite application.
+
+ Use this as replacement for bcrypt that does not need a c implementation
+ of a modified blowfish crypto algo.
+
+ Example usage:
+
+ >>> pbkdf2_hex('what i want to hash', 'the random salt')
+ 'fa7cc8a2b0a932f8e6ea42f9787e9d36e592e0c222ada6a9'
+
+ How to use this:
+
+ 1. Use a constant time string compare function to compare the stored hash
+ with the one you're generating::
+
+ def safe_str_cmp(a, b):
+ if len(a) != len(b):
+ return False
+ rv = 0
+ for x, y in izip(a, b):
+ rv |= ord(x) ^ ord(y)
+ return rv == 0
+
+ 2. Use `os.urandom` to generate a proper salt of at least 8 byte.
+ Use a unique salt per hashed password.
+
+ 3. Store ``algorithm$salt:costfactor$hash`` in the database so that
+ you can upgrade later easily to a different algorithm if you need
+ one. For instance ``PBKDF2-256$thesalt:10000$deadbeef...``.
+
+
+ :copyright: (c) Copyright 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import hmac
+import hashlib
+from struct import Struct
+from operator import xor
+from itertools import izip, starmap
+
+
+_pack_int = Struct('>I').pack
+
+
+def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
+ """Like :func:`pbkdf2_bin` but returns a hex encoded string."""
+ return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).encode('hex')
+
+
+def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
+ """Returns a binary digest for the PBKDF2 hash algorithm of `data`
+ with the given `salt`. It iterates `iterations` time and produces a
+ key of `keylen` bytes. By default SHA-1 is used as hash function,
+ a different hashlib `hashfunc` can be provided.
+ """
+ hashfunc = hashfunc or hashlib.sha1
+ mac = hmac.new(data, None, hashfunc)
+ def _pseudorandom(x, mac=mac):
+ h = mac.copy()
+ h.update(x)
+ return map(ord, h.digest())
+ buf = []
+ for block in xrange(1, -(-keylen // mac.digest_size) + 1):
+ rv = u = _pseudorandom(salt + _pack_int(block))
+ for i in xrange(iterations - 1):
+ u = _pseudorandom(''.join(map(chr, u)))
+ rv = list(starmap(xor, izip(rv, u)))
+ buf.extend(rv)
+ return ''.join(map(chr, buf))[:keylen]
+
+
+def safe_str_cmp(a, b):
+ if len(a) != len(b):
+ return False
+ rv = 0
+ for x, y in izip(a, b):
+ rv |= ord(x) ^ ord(y)
+ return rv == 0
+
+
+
+def test():
+ failed = []
+ def check(data, salt, iterations, keylen, expected):
+ rv = pbkdf2_hex(data, salt, iterations, keylen)
+ if rv != expected:
+ print 'Test failed:'
+ print ' Expected: %s' % expected
+ print ' Got: %s' % rv
+ print ' Parameters:'
+ print ' data=%s' % data
+ print ' salt=%s' % salt
+ print ' iterations=%d' % iterations
+ print
+ failed.append(1)
+
+ # From RFC 6070
+ check('password', 'salt', 1, 20,
+ '0c60c80f961f0e71f3a9b524af6012062fe037a6')
+ check('password', 'salt', 2, 20,
+ 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957')
+ check('password', 'salt', 4096, 20,
+ '4b007901b765489abead49d926f721d065a429c1')
+ check('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt',
+ 4096, 25, '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038')
+ check('pass\x00word', 'sa\x00lt', 4096, 16,
+ '56fa6aa75548099dcc37d7f03425e0c3')
+ # This one is from the RFC but it just takes for ages
+ ##check('password', 'salt', 16777216, 20,
+ ## 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984')
+
+ # From Crypt-PBKDF2
+ check('password', 'ATHENA.MIT.EDUraeburn', 1, 16,
+ 'cdedb5281bb2f801565a1122b2563515')
+ check('password', 'ATHENA.MIT.EDUraeburn', 1, 32,
+ 'cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837')
+ check('password', 'ATHENA.MIT.EDUraeburn', 2, 16,
+ '01dbee7f4a9e243e988b62c73cda935d')
+ check('password', 'ATHENA.MIT.EDUraeburn', 2, 32,
+ '01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86')
+ check('password', 'ATHENA.MIT.EDUraeburn', 1200, 32,
+ '5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13')
+ check('X' * 64, 'pass phrase equals block size', 1200, 32,
+ '139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1')
+ check('X' * 65, 'pass phrase exceeds block size', 1200, 32,
+ '9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a')
+
+ raise SystemExit(bool(failed))
+
+
+if __name__ == '__main__':
+ test()
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/static/new/css/parsley.css b/wqflask/wqflask/static/new/css/parsley.css
new file mode 100644
index 00000000..7d244579
--- /dev/null
+++ b/wqflask/wqflask/static/new/css/parsley.css
@@ -0,0 +1,20 @@
+/* Adapted from parsleyjs.org/documentation.html#parsleyclasses */
+
+input.parsley-success, textarea.parsley-success {
+ color: #468847 !important;
+ background-color: #DFF0D8 !important;
+ border: 1px solid #D6E9C6 !important;
+}
+input.parsley-error, textarea.parsley-error {
+ color: #B94A48 !important;
+ background-color: #F2DEDE !important;
+ border: 1px solid #EED3D7 !important;
+}
+ul.parsley-error-list {
+ font-size: 11px;
+ margin: 2px;
+ list-style-type:none;
+}
+ul.parsley-error-list li {
+ line-height: 11px;
+} \ No newline at end of file
diff --git a/wqflask/wqflask/static/new/javascript/password_strength.coffee b/wqflask/wqflask/static/new/javascript/password_strength.coffee
new file mode 100644
index 00000000..0bee5836
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/password_strength.coffee
@@ -0,0 +1,33 @@
+$ ->
+
+
+ $("#password").keyup ->
+ passtext = $(this).val()
+ result = zxcvbn(passtext)
+ if passtext.length < 6
+ $("#password_strength").html('')
+ $("#password_alert").fadeOut()
+ else
+ word = word_score(result.score)
+ crack_time = result.crack_time_display
+ if crack_time == "instant"
+ crack_time = "a second"
+ display = "This is #{word} password. It can be cracked in #{crack_time}."
+ $("#password_strength").html(display)
+ $("#password_alert").fadeIn()
+
+
+
+ word_score = (num_score) ->
+ num_score = parseInt(num_score)
+ console.log("num_score is:", num_score)
+ mapping =
+ 0: "a <strong>terrible</strong>"
+ 1: "a <strong>bad</strong>"
+ 2: "a <strong>mediocre</strong>"
+ 3: "a <strong>good</strong>"
+ 4: "an <strong>excellent</strong>"
+ console.log("mapping is:", mapping)
+ result = mapping[num_score]
+ console.log("result is:", result)
+ return result \ No newline at end of file
diff --git a/wqflask/wqflask/static/new/javascript/password_strength.js b/wqflask/wqflask/static/new/javascript/password_strength.js
new file mode 100644
index 00000000..166e1125
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/password_strength.js
@@ -0,0 +1,42 @@
+// Generated by CoffeeScript 1.6.1
+(function() {
+
+ $(function() {
+ var word_score;
+ $("#password").keyup(function() {
+ var crack_time, display, passtext, result, word;
+ passtext = $(this).val();
+ result = zxcvbn(passtext);
+ if (passtext.length < 6) {
+ $("#password_strength").html('');
+ return $("#password_alert").fadeOut();
+ } else {
+ word = word_score(result.score);
+ crack_time = result.crack_time_display;
+ if (crack_time === "instant") {
+ crack_time = "a second";
+ }
+ display = "This is " + word + " password. It can be cracked in " + crack_time + ".";
+ $("#password_strength").html(display);
+ return $("#password_alert").fadeIn();
+ }
+ });
+ return word_score = function(num_score) {
+ var mapping, result;
+ num_score = parseInt(num_score);
+ console.log("num_score is:", num_score);
+ mapping = {
+ 0: "a <strong>terrible</strong>",
+ 1: "a <strong>bad</strong>",
+ 2: "a <strong>mediocre</strong>",
+ 3: "a <strong>good</strong>",
+ 4: "an <strong>excellent</strong>"
+ };
+ console.log("mapping is:", mapping);
+ result = mapping[num_score];
+ console.log("result is:", result);
+ return result;
+ };
+ });
+
+}).call(this);
diff --git a/wqflask/wqflask/static/new/js_external/parsley.min.js b/wqflask/wqflask/static/new/js_external/parsley.min.js
new file mode 100644
index 00000000..ab85c683
--- /dev/null
+++ b/wqflask/wqflask/static/new/js_external/parsley.min.js
@@ -0,0 +1,35 @@
+/* Parsley dist/parsley.min.js build version 1.1.18 http://parsleyjs.org */
+!function(d){var h=function(a){this.messages={defaultMessage:"This value seems to be invalid.",type:{email:"This value should be a valid email.",url:"This value should be a valid url.",urlstrict:"This value should be a valid url.",number:"This value should be a valid number.",digits:"This value should be digits.",dateIso:"This value should be a valid date (YYYY-MM-DD).",alphanum:"This value should be alphanumeric.",phone:"This value should be a valid phone number."},notnull:"This value should not be null.",
+notblank:"This value should not be blank.",required:"This value is required.",regexp:"This value seems to be invalid.",min:"This value should be greater than or equal to %s.",max:"This value should be lower than or equal to %s.",range:"This value should be between %s and %s.",minlength:"This value is too short. It should have %s characters or more.",maxlength:"This value is too long. It should have %s characters or less.",rangelength:"This value length is invalid. It should be between %s and %s characters long.",
+mincheck:"You must select at least %s choices.",maxcheck:"You must select %s choices or less.",rangecheck:"You must select between %s and %s choices.",equalto:"This value should be the same."};this.init(a)};h.prototype={constructor:h,validators:{notnull:function(a){return 0<a.length},notblank:function(a){return"string"===typeof a&&""!==a.replace(/^\s+/g,"").replace(/\s+$/g,"")},required:function(a){if("object"===typeof a){for(var b in a)if(this.required(a[b]))return!0;return!1}return this.notnull(a)&&
+this.notblank(a)},type:function(a,b){var c;switch(b){case "number":c=/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;break;case "digits":c=/^\d+$/;break;case "alphanum":c=/^\w+$/;break;case "email":c=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))){2,6}$/i;
+break;case "url":a=/(https?|s?ftp|git)/i.test(a)?a:"http://"+a;case "urlstrict":c=/^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
+break;case "dateIso":c=/^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/;break;case "phone":c=/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/;break;default:return!1}return""!==a?c.test(a):!1},regexp:function(a,b,c){return RegExp(b,c.options.regexpFlag||"").test(a)},minlength:function(a,b){return a.length>=b},maxlength:function(a,b){return a.length<=b},rangelength:function(a,b){return this.minlength(a,b[0])&&this.maxlength(a,b[1])},
+min:function(a,b){return Number(a)>=b},max:function(a,b){return Number(a)<=b},range:function(a,b){return a>=b[0]&&a<=b[1]},equalto:function(a,b,c){c.options.validateIfUnchanged=!0;return a===d(b).val()},remote:function(a,b,c){var f={},g={};f[c.$element.attr("name")]=a;"undefined"!==typeof c.options.remoteDatatype&&(g={dataType:c.options.remoteDatatype});var m=function(a,b){"undefined"!==typeof b&&("undefined"!==typeof c.Validator.messages.remote&&b!==c.Validator.messages.remote)&&d(c.ulError+" .remote").remove();
+c.updtConstraint({name:"remote",valid:a},b);c.manageValidationResult()},n=function(a){if("object"===typeof a)return a;try{a=d.parseJSON(a)}catch(b){}return a},e=function(a){return"object"===typeof a&&null!==a?"undefined"!==typeof a.error?a.error:"undefined"!==typeof a.message?a.message:null:null};d.ajax(d.extend({},{url:b,data:f,type:c.options.remoteMethod||"GET",success:function(a){a=n(a);m(1===a||!0===a||"object"===typeof a&&null!==a&&"undefined"!==typeof a.success,e(a))},error:function(a){a=n(a);
+m(!1,e(a))}},g));return null},mincheck:function(a,b){return this.minlength(a,b)},maxcheck:function(a,b){return this.maxlength(a,b)},rangecheck:function(a,b){return this.rangelength(a,b)}},init:function(a){var b=a.validators;a=a.messages;for(var c in b)this.addValidator(c,b[c]);for(c in a)this.addMessage(c,a[c])},formatMesssage:function(a,b){if("object"===typeof b){for(var c in b)a=this.formatMesssage(a,b[c]);return a}return"string"===typeof a?a.replace(/%s/i,b):""},addValidator:function(a,b){this.validators[a]=
+b},addMessage:function(a,b,c){if("undefined"!==typeof c&&!0===c)this.messages.type[a]=b;else if("type"===a)for(var d in b)this.messages.type[d]=b[d];else this.messages[a]=b}};var j=function(a,b,c){this.options=b;this.Validator=new h(b);if("ParsleyFieldMultiple"===c)return this;this.init(a,c||"ParsleyField")};j.prototype={constructor:j,init:function(a,b){this.type=b;this.valid=!0;this.element=a;this.validatedOnce=!1;this.$element=d(a);this.val=this.$element.val();this.isRequired=!1;this.constraints=
+{};"undefined"===typeof this.isRadioOrCheckbox&&(this.isRadioOrCheckbox=!1,this.hash=this.generateHash(),this.errorClassHandler=this.options.errors.classHandler(a,this.isRadioOrCheckbox)||this.$element);this.ulErrorManagement();this.bindHtml5Constraints();this.addConstraints();this.hasConstraints()&&this.bindValidationEvents()},setParent:function(a){this.$parent=d(a)},getParent:function(){return this.$parent},bindHtml5Constraints:function(){if(this.$element.hasClass("required")||this.$element.prop("required"))this.options.required=
+!0;"undefined"!==typeof this.$element.attr("type")&&RegExp(this.$element.attr("type"),"i").test("email url number range")&&(this.options.type=this.$element.attr("type"),RegExp(this.options.type,"i").test("number range")&&(this.options.type="number","undefined"!==typeof this.$element.attr("min")&&this.$element.attr("min").length&&(this.options.min=this.$element.attr("min")),"undefined"!==typeof this.$element.attr("max")&&this.$element.attr("max").length&&(this.options.max=this.$element.attr("max"))));
+"string"===typeof this.$element.attr("pattern")&&this.$element.attr("pattern").length&&(this.options.regexp=this.$element.attr("pattern"))},addConstraints:function(){for(var a in this.options){var b={};b[a]=this.options[a];this.addConstraint(b,!0)}},addConstraint:function(a,b){for(var c in a)c=c.toLowerCase(),"function"===typeof this.Validator.validators[c]&&(this.constraints[c]={name:c,requirements:a[c],valid:null},"required"===c&&(this.isRequired=!0),this.addCustomConstraintMessage(c));"undefined"===
+typeof b&&this.bindValidationEvents()},updateConstraint:function(a,b){for(var c in a)this.updtConstraint({name:c,requirements:a[c],valid:null},b)},updtConstraint:function(a,b){this.constraints[a.name]=d.extend(!0,this.constraints[a.name],a);"string"===typeof b&&(this.Validator.messages[a.name]=b);this.bindValidationEvents()},removeConstraint:function(a){a=a.toLowerCase();delete this.constraints[a];"required"===a&&(this.isRequired=!1);this.hasConstraints()?this.bindValidationEvents():"ParsleyForm"===
+typeof this.getParent()?this.getParent().removeItem(this.$element):this.destroy()},addCustomConstraintMessage:function(a){var b=a+("type"===a&&"undefined"!==typeof this.options[a]?this.options[a].charAt(0).toUpperCase()+this.options[a].substr(1):"")+"Message";"undefined"!==typeof this.options[b]&&this.Validator.addMessage("type"===a?this.options[a]:a,this.options[b],"type"===a)},bindValidationEvents:function(){this.valid=null;this.$element.addClass("parsley-validated");this.$element.off("."+this.type);
+this.options.remote&&!/change/i.test(this.options.trigger)&&(this.options.trigger=!this.options.trigger?"change":" change");var a=(!this.options.trigger?"":this.options.trigger)+(/key/i.test(this.options.trigger)?"":" keyup");this.$element.is("select")&&(a+=/change/i.test(a)?"":" change");a=a.replace(/^\s+/g,"").replace(/\s+$/g,"");this.$element.on((a+" ").split(" ").join("."+this.type+" "),!1,d.proxy(this.eventValidation,this))},generateHash:function(){return"parsley-"+(Math.random()+"").substring(2)},
+getHash:function(){return this.hash},getVal:function(){return this.$element.data("value")||this.$element.val()},eventValidation:function(a){var b=this.getVal();if("keyup"===a.type&&!/keyup/i.test(this.options.trigger)&&!this.validatedOnce||"change"===a.type&&!/change/i.test(this.options.trigger)&&!this.validatedOnce||!this.isRadioOrCheckbox&&this.getLength(b)<this.options.validationMinlength&&!this.validatedOnce)return!0;this.validate()},getLength:function(a){return!a||!a.hasOwnProperty("length")?
+0:a.length},isValid:function(){return this.validate(!1)},hasConstraints:function(){for(var a in this.constraints)return!0;return!1},validate:function(a){var b=this.getVal(),c=null;if(!this.hasConstraints())return null;if(this.options.listeners.onFieldValidate(this.element,this)||""===b&&!this.isRequired)return this.reset(),null;if(!this.needsValidation(b))return this.valid;c=this.applyValidators();("undefined"!==typeof a?a:this.options.showErrors)&&this.manageValidationResult();return c},needsValidation:function(a){if(!this.options.validateIfUnchanged&&
+null!==this.valid&&this.val===a&&this.validatedOnce)return!1;this.val=a;return this.validatedOnce=!0},applyValidators:function(){var a=null,b;for(b in this.constraints){var c=this.Validator.validators[this.constraints[b].name](this.val,this.constraints[b].requirements,this);!1===c?(a=!1,this.constraints[b].valid=a,this.options.listeners.onFieldError(this.element,this.constraints,this)):!0===c&&(this.constraints[b].valid=!0,a=!1!==a,!1===this.options.listeners.onFieldSuccess(this.element,this.constraints,
+this)&&(a=!1))}return a},manageValidationResult:function(){var a=null,b;for(b in this.constraints)!1===this.constraints[b].valid?(this.manageError(this.constraints[b]),a=!1):!0===this.constraints[b].valid&&(this.removeError(this.constraints[b].name),a=!1!==a);this.valid=a;if(!0===this.valid)return this.removeErrors(),this.errorClassHandler.removeClass(this.options.errorClass).addClass(this.options.successClass),!0;if(!1===this.valid)return this.errorClassHandler.removeClass(this.options.successClass).addClass(this.options.errorClass),
+!1;this.ulError&&0===d(this.ulError).children().length&&this.removeErrors();return a},ulErrorManagement:function(){this.ulError="#"+this.hash;this.ulTemplate=d(this.options.errors.errorsWrapper).attr("id",this.hash).addClass("parsley-error-list")},removeError:function(a){a=this.ulError+" ."+a;var b=this;this.options.animate?d(a).fadeOut(this.options.animateDuration,function(){d(this).remove();b.ulError&&0===d(b.ulError).children().length&&b.removeErrors()}):d(a).remove()},addError:function(a){for(var b in a){var c=
+d(this.options.errors.errorElem).addClass(b);d(this.ulError).append(this.options.animate?d(c).html(a[b]).hide().fadeIn(this.options.animateDuration):d(c).html(a[b]))}},removeErrors:function(){this.options.animate?d(this.ulError).fadeOut(this.options.animateDuration,function(){d(this).remove()}):d(this.ulError).remove()},reset:function(){this.valid=null;this.removeErrors();this.validatedOnce=!1;this.errorClassHandler.removeClass(this.options.successClass).removeClass(this.options.errorClass);for(var a in this.constraints)this.constraints[a].valid=
+null;return this},manageError:function(a){d(this.ulError).length||this.manageErrorContainer();if(!("required"===a.name&&null!==this.getVal()&&0<this.getVal().length)&&(!this.isRequired||!("required"!==a.name&&(null===this.getVal()||0===this.getVal().length)))){var b=a.name,c=!1!==this.options.errorMessage?"custom-error-message":b,f={};a=!1!==this.options.errorMessage?this.options.errorMessage:"type"===a.name?this.Validator.messages[b][a.requirements]:"undefined"===typeof this.Validator.messages[b]?
+this.Validator.messages.defaultMessage:this.Validator.formatMesssage(this.Validator.messages[b],a.requirements);d(this.ulError+" ."+c).length||(f[c]=a,this.addError(f))}},manageErrorContainer:function(){var a=this.options.errorContainer||this.options.errors.container(this.element,this.isRadioOrCheckbox),b=this.options.animate?this.ulTemplate.show():this.ulTemplate;"undefined"!==typeof a?d(a).append(b):!this.isRadioOrCheckbox?this.$element.after(b):this.$element.parent().after(b)},addListener:function(a){for(var b in a)this.options.listeners[b]=
+a[b]},destroy:function(){this.$element.removeClass("parsley-validated");this.reset().$element.off("."+this.type).removeData(this.type)}};var l=function(a,b,c){this.initMultiple(a,b);this.inherit(a,b);this.Validator=new h(b);this.init(a,c||"ParsleyFieldMultiple")};l.prototype={constructor:l,initMultiple:function(a,b){this.element=a;this.$element=d(a);this.group=b.group||!1;this.hash=this.getName();this.siblings=this.group?'[data-group="'+this.group+'"]':'input[name="'+this.$element.attr("name")+'"]';
+this.isRadioOrCheckbox=!0;this.isRadio=this.$element.is("input[type=radio]");this.isCheckbox=this.$element.is("input[type=checkbox]");this.errorClassHandler=b.errors.classHandler(a,this.isRadioOrCheckbox)||this.$element.parent()},inherit:function(a,b){var c=new j(a,b,"ParsleyFieldMultiple"),d;for(d in c)"undefined"===typeof this[d]&&(this[d]=c[d])},getName:function(){if(this.group)return"parsley-"+this.group;if("undefined"===typeof this.$element.attr("name"))throw"A radio / checkbox input must have a data-group attribute or a name to be Parsley validated !";
+return"parsley-"+this.$element.attr("name").replace(/(:|\.|\[|\])/g,"")},getVal:function(){if(this.isRadio)return d(this.siblings+":checked").val()||"";if(this.isCheckbox){var a=[];d(this.siblings+":checked").each(function(){a.push(d(this).val())});return a}},bindValidationEvents:function(){this.valid=null;this.$element.addClass("parsley-validated");this.$element.off("."+this.type);var a=this,b=(!this.options.trigger?"":this.options.trigger)+(/change/i.test(this.options.trigger)?"":" change"),b=b.replace(/^\s+/g,
+"").replace(/\s+$/g,"");d(this.siblings).each(function(){d(this).on(b.split(" ").join("."+a.type+" "),!1,d.proxy(a.eventValidation,a))})}};var k=function(a,b,c){this.init(a,b,c||"parsleyForm")};k.prototype={constructor:k,init:function(a,b,c){this.type=c;this.items=[];this.$element=d(a);this.options=b;var f=this;this.$element.find(b.inputs).each(function(){f.addItem(this)});this.$element.on("submit."+this.type,!1,d.proxy(this.validate,this))},addListener:function(a){for(var b in a)if(/Field/.test(b))for(var c=
+0;c<this.items.length;c++)this.items[c].addListener(a);else this.options.listeners[b]=a[b]},addItem:function(a){if(d(a).is(this.options.excluded))return!1;a=d(a).parsley(this.options);a.setParent(this);this.items.push(a)},removeItem:function(a){a=d(a).parsley();for(var b=0;b<this.items.length;b++)if(this.items[b].hash===a.hash)return this.items[b].destroy(),this.items.splice(b,1),!0;return!1},validate:function(a){var b=!0;this.focusedField=!1;for(var c=0;c<this.items.length;c++)if("undefined"!==typeof this.items[c]&&
+!1===this.items[c].validate()&&(b=!1,!this.focusedField&&"first"===this.options.focus||"last"===this.options.focus))this.focusedField=this.items[c].$element;this.focusedField&&!b&&this.focusedField.focus();a=this.options.listeners.onFormSubmit(b,a,this);return"undefined"!==typeof a?a:b},isValid:function(){for(var a=0;a<this.items.length;a++)if(!1===this.items[a].isValid())return!1;return!0},removeErrors:function(){for(var a=0;a<this.items.length;a++)this.items[a].parsley("reset")},destroy:function(){for(var a=
+0;a<this.items.length;a++)this.items[a].destroy();this.$element.off("."+this.type).removeData(this.type)},reset:function(){for(var a=0;a<this.items.length;a++)this.items[a].reset()}};d.fn.parsley=function(a,b){function c(c,g){var e=d(c).data(g);if(!e){switch(g){case "parsleyForm":e=new k(c,f,"parsleyForm");break;case "parsleyField":e=new j(c,f,"parsleyField");break;case "parsleyFieldMultiple":e=new l(c,f,"parsleyFieldMultiple");break;default:return}d(c).data(g,e)}return"string"===typeof a&&"function"===
+typeof e[a]?(e=e[a](b),"undefined"!==typeof e?e:d(c)):e}var f=d.extend(!0,{},d.fn.parsley.defaults,"undefined"!==typeof window.ParsleyConfig?window.ParsleyConfig:{},a,this.data()),g=null;d(this).is("form")||!0===d(this).data("bind")?g=c(d(this),"parsleyForm"):d(this).is(f.inputs)&&!d(this).is(f.excluded)&&(g=c(d(this),!d(this).is("input[type=radio], input[type=checkbox]")?"parsleyField":"parsleyFieldMultiple"));return"function"===typeof b?b():g};d.fn.parsley.Constructor=k;d.fn.parsley.defaults={inputs:"input, textarea, select",
+excluded:"input[type=hidden], input[type=file], :disabled",trigger:!1,animate:!0,animateDuration:300,focus:"first",validationMinlength:3,successClass:"parsley-success",errorClass:"parsley-error",errorMessage:!1,validators:{},showErrors:!0,messages:{},validateIfUnchanged:!1,errors:{classHandler:function(){},container:function(){},errorsWrapper:"<ul></ul>",errorElem:"<li></li>"},listeners:{onFieldValidate:function(){return!1},onFormSubmit:function(){},onFieldError:function(){},onFieldSuccess:function(){}}};
+d(window).on("load",function(){d('[data-validate="parsley"]').each(function(){d(this).parsley()})})}(window.jQuery||window.Zepto);
diff --git a/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js b/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js
new file mode 100644
index 00000000..404944d3
--- /dev/null
+++ b/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js
@@ -0,0 +1 @@
+(function(){var a;a=function(){var a,b;b=document.createElement("script");b.src="//dl.dropbox.com/u/209/zxcvbn/zxcvbn.js";b.type="text/javascript";b.async=!0;a=document.getElementsByTagName("script")[0];return a.parentNode.insertBefore(b,a)};null!=window.attachEvent?window.attachEvent("onload",a):window.addEventListener("load",a,!1)}).call(this);
diff --git a/wqflask/wqflask/templates/admin/ind_user_manager.html b/wqflask/wqflask/templates/admin/ind_user_manager.html
index 9776af0b..2fe4a002 100644
--- a/wqflask/wqflask/templates/admin/ind_user_manager.html
+++ b/wqflask/wqflask/templates/admin/ind_user_manager.html
@@ -30,6 +30,16 @@
<th>Value</th>
</tr>
</thead>-->
+
+ <tr>
+ <td>Name</td>
+ <td>{{ user.name }}</td>
+ </tr>
+
+ <tr>
+ <td>Organization</td>
+ <td>{{ user.organization }}</td>
+ </tr>
<tr>
diff --git a/wqflask/wqflask/templates/admin/user_manager.html b/wqflask/wqflask/templates/admin/user_manager.html
index 14cd12e0..1308ff4b 100644
--- a/wqflask/wqflask/templates/admin/user_manager.html
+++ b/wqflask/wqflask/templates/admin/user_manager.html
@@ -16,20 +16,20 @@
<table class="table table-hover">
<thead>
<tr>
- <th>ID</th>
<th>Email</th>
- <th>Confirmed at</th>
+ <th>Organization</th>
<th>Active</th>
+ <th>Confirmed</th>
</tr>
</thead>
{% for user in users %}
<tr>
- <td title="{{ user.__dict__ }}">
- <a href="{{ url_for('manage_user', user_id=user.id) }}">{{ user.id }}</a>
+ <td title="{{ user.id }}">
+ <a href="{{ url_for('manage_user', user_id=user.id) }}">{{ user.email_address }}</a>
</td>
- <td>{{ user.email }}</td>
- <td>{{ user.confirmed_at }}</td>
- <td>{{ user.active }}</td>
+ <td>{{ user.organization }}</td>
+ <td>{{ 'Yes' if user.active else 'No' }}</td>
+ <td title="{{ user.confirmed }}">{{ 'True' if user.confirmed else 'False' }}</td>
</tr>
{% endfor %}
</table>
diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html
index cbed5859..8efba6a7 100644
--- a/wqflask/wqflask/templates/base.html
+++ b/wqflask/wqflask/templates/base.html
@@ -19,16 +19,46 @@
<link rel="stylesheet" type="text/css" href="/static/packages/colorbox/example4/colorbox.css" />
<link rel="stylesheet" type="text/css" href="/static/new/css/main.css" />
+ <link rel="stylesheet" type="text/css" href="/static/new/css/parsley.css" />
{% block css %}
{% endblock %}
</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">
@@ -69,10 +99,10 @@
<a href="/links">Links</a>
</li>
<li class="">
- {% if g.identity.name=="anon" %}
- <a id="login_out" class="modalize" href="/login">Sign in</a>
+ {% if g.user_session.logged_in %}
+ <a id="login_out" href="/n/logout">Sign out</a>
{% else %}
- <a id="login_out" href="/logout">Sign out</a>
+ <a id="login_in" href="/n/login">Sign in</a>
{% endif %}
</li>
</ul>
@@ -180,12 +210,13 @@
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js" type="text/javascript"></script>
<script language="javascript" type="text/javascript" src="/static/packages/colorbox/jquery.colorbox.js"></script>
- <script type="text/javascript" src="/static/new/javascript/login.js"></script>
+ <!--<script type="text/javascript" src="/static/new/javascript/login.js"></script>-->
+
+ <script type="text/javascript" src="/static/new/js_external/parsley.min.js"></script>
+
{% block js %}
{% endblock %}
-
-
</body>
</html>
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/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/_scripts.html b/wqflask/wqflask/templates/new_security/_scripts.html
new file mode 100644
index 00000000..5fefe305
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/_scripts.html
@@ -0,0 +1 @@
+<!--<script type="text/javascript" src="/static/new/javascript/login.js"></script>-->
diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html
new file mode 100644
index 00000000..f232ccbc
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/login_user.html
@@ -0,0 +1,83 @@
+{% 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/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html
new file mode 100644
index 00000000..2a02e7ca
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/register_user.html
@@ -0,0 +1,113 @@
+{% 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>
+
+ <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">
+ <strong>Please note:</strong>
+ <ul>
+ {% for error in errors %}
+ <li>{{error}}</li>
+ {% endfor %}
+ </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">
+ <input id="email_address" name="email_address" class="focused" type="text" value="{{values.email_address}}"
+ 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">
+ <input id="full_name" name="full_name" type="text" value="{{values.full_name}}"
+ 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 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="Create account">
+ </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/registered.html b/wqflask/wqflask/templates/new_security/registered.html
new file mode 100644
index 00000000..391a6044
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/registered.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+ <header class="jumbotron subhead" id="overview">
+ <div class="container">
+ <h1>Thank you</h1>
+ <p class="lead">
+ Thanks for verifying.
+ </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>"GeneNetwork email verification"</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 %}
+ <!--<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/thank_you.html b/wqflask/wqflask/templates/new_security/thank_you.html
new file mode 100644
index 00000000..97cb7807
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/thank_you.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+ <header class="jumbotron subhead" id="overview">
+ <div class="container">
+ <h1>Thank you</h1>
+ <p class="lead">
+ Thanks for verifying.
+ </p>
+ </div>
+ </header>
+
+ <div class="container">
+ <div class="page-header">
+ <h3>You are done registering</h3>
+ </div>
+
+ <p>Enjoy using the site.</p>
+
+ <p>Go to the <a href="{{ url_for("index_page") }}">homepage</a></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/verified.html b/wqflask/wqflask/templates/new_security/verified.html
new file mode 100644
index 00000000..97cb7807
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/verified.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+ <header class="jumbotron subhead" id="overview">
+ <div class="container">
+ <h1>Thank you</h1>
+ <p class="lead">
+ Thanks for verifying.
+ </p>
+ </div>
+ </header>
+
+ <div class="container">
+ <div class="page-header">
+ <h3>You are done registering</h3>
+ </div>
+
+ <p>Enjoy using the site.</p>
+
+ <p>Go to the <a href="{{ url_for("index_page") }}">homepage</a></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/security/register_user.html b/wqflask/wqflask/templates/security/register_user.html
index 3cd021b0..8e6908ff 100644
--- a/wqflask/wqflask/templates/security/register_user.html
+++ b/wqflask/wqflask/templates/security/register_user.html
@@ -28,8 +28,22 @@
</div>
{{ render_only_errors(register_user_form.email) }}
</div>
+
+ <div class="control-group">
+ <label class="control-label" for="email">Name</label>
+ <div class="controls">
+ <input id="name" name="name" type="text" value="">
+ </div>
+ </div>
<div class="control-group">
+ <label class="control-label" for="email">Organization</label>
+ <div class="controls">
+ <input id="organization" name="organization" 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="">
@@ -49,7 +63,7 @@
<div class="control-group">
<div class="controls"">
- <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in">
+ <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Create account">
</div>
</div>
diff --git a/wqflask/wqflask/tracer.py b/wqflask/wqflask/tracer.py
new file mode 100644
index 00000000..43a0f15e
--- /dev/null
+++ b/wqflask/wqflask/tracer.py
@@ -0,0 +1,41 @@
+from __future__ import absolute_import, division, print_function
+
+print("At top of tracer")
+
+import sys
+
+####################################################################################
+
+# Originally based on http://stackoverflow.com/a/8315566
+def tracefunc(frame, event, arg, indent=[0]):
+
+ func = dict(funcname = frame.f_code.co_name,
+ filename = frame.f_code.co_filename,
+ lineno = frame.f_lineno)
+
+ #These are too common to bother printing...
+ too_common = (
+ '/home/sam/ve27/local/lib/python2.7/site-packages/werkzeug/',
+ '/home/sam/ve27/local/lib/python2.7/site-packages/jinja2/',
+ )
+
+
+ if func['filename'].startswith(too_common):
+ return tracefunc
+
+ info = "{funcname} [{filename}: {lineno}]".format(**func)
+
+ if event == "call":
+ indent[0] += 2
+ #print("-" * indent[0] + "> call function", frame.f_code.co_name)
+ print("-" * indent[0] + "> call function:", info)
+ elif event == "return":
+ print("<" + "-" * indent[0], "exit function:", info)
+ indent[0] -= 2
+ return tracefunc
+
+def turn_on():
+ sys.settrace(tracefunc)
+ print("Tracing turned on!!!!")
+####################################################################################
+
diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py
index 9e666bbd..a2dff7f2 100644
--- a/wqflask/wqflask/user_manager.py
+++ b/wqflask/wqflask/user_manager.py
@@ -7,18 +7,80 @@ from __future__ import print_function, division, absolute_import
"""
-from wqflask import model
+import os
+import hashlib
+import datetime
+import time
+
+import uuid
+import hashlib
+import hmac
+import base64
+
+import simplejson as json
+
+from redis import StrictRedis
+Redis = StrictRedis()
+
+
+from flask import (Flask, g, render_template, url_for, request, make_response,
+ redirect, flash)
+
+from wqflask import app
-from flask import Flask, g
from pprint import pformat as pf
+from wqflask import pbkdf2
+
+from wqflask.database import db_session
+
+from wqflask import model
+
+from utility import Bunch, Struct
+
+
+
from base.data_set import create_datasets_list
-#from app import db
-print("globals are:", globals())
+def timestamp():
+ return datetime.datetime.utcnow().isoformat()
+
+
+
+
+class UserSession(object):
+ cookie_name = 'session_id'
+
+ def __init__(self):
+ cookie = request.cookies.get(self.cookie_name)
+ if not cookie:
+ self.logged_in = False
+ return
+ else:
+ session_id, separator, session_id_signature = cookie.partition(':')
+ assert len(session_id) == 36, "Is session_id a uuid?"
+ assert separator == ":", "Expected a : here"
+ assert session_id_signature == actual_hmac_creation(session_id), "Uh-oh, someone tampering with the cookie?"
+ self.redis_key = "session_id:" + session_id
+ print("self.redis_key is:", self.redis_key)
+ self.session_id = session_id
+ 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()
+
class UsersManager(object):
def __init__(self):
self.users = model.User.query.all()
@@ -28,7 +90,7 @@ class UsersManager(object):
class UserManager(object):
def __init__(self, kw):
- self.user_id = int(kw['user_id'])
+ self.user_id = kw['user_id']
print("In UserManager locals are:", pf(locals()))
#self.user = model.User.get(user_id)
#print("user is:", user)
@@ -43,6 +105,260 @@ 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.')
+
+ 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)
+
+ 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()
+ 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):
+ verification_code = str(uuid.uuid4())
+ key = "verification_code:" + verification_code
+
+ data = json.dumps(dict(id=self.new_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",
+ verification_code = verification_code)
+ send_email(to, subject, body)
+
+class Password(object):
+ def __init__(self, unencrypted_password, salt, iterations, keylength, hashfunc):
+ 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(),
+ ip_address = request.remote_addr,
+ user_agent = request.headers.get('User-Agent'))
+
+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.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
+ 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")
+
+ 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:
+ 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
+
+@app.route("/n/logout")
+def logout():
+ print("Logging out...")
+ UserSession().delete_session()
+ flash("You are now logged out. We hope you come back soon!")
+ response = make_response(redirect(url_for('index_page')))
+ # Delete the cookie
+ response.set_cookie(UserSession.cookie_name, '', expires=0)
+ return response
+
+
+################################# 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(stringy):
+ """Helper function to create the actual hmac"""
+
+ secret = app.config['SECRET_HMAC_CODE']
+
+ hmaced = hmac.new(secret, stringy, 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
+# salts aren't immediately known"""
+# secret_salt = app.confing['SECRET_SALT']
+# assert len(user_salt) == 32
+# assert len(secret_salt) == 32
+# combined = ""
+# for x, y in user_salt, secret_salt:
+# combined = combined + x + y
+# return combined
+
class GroupsManager(object):
@@ -54,3 +370,13 @@ class RolesManager(object):
def __init__(self):
self.roles = model.Role.query.all()
print("Roles are:", self.roles)
+
+
+#class Password(object):
+# """To generate a master password: dd if=/dev/urandom bs=32 count=1 > master_salt"""
+#
+# master_salt =
+
+
+
+
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index d57bbaa7..deccf459 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -23,7 +23,8 @@ import sqlalchemy
from wqflask import app
-from flask import render_template, request, make_response, Response, Flask, g, config, jsonify
+from flask import (render_template, request, make_response, Response,
+ Flask, g, config, jsonify, redirect, url_for)
from wqflask import search_results
from base.data_set import DataSet # Used by YAML in marker_regression
@@ -50,6 +51,11 @@ 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
+# tracer.turn_on()
@app.route("/")
def index_page():
@@ -261,15 +267,15 @@ def sharing_info_page():
template_vars = SharingInfoPage.SharingInfoPage(fd)
return template_vars
-# Take this out or secure it before going 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/users")
def manage_users():
@@ -287,6 +293,55 @@ def manage_groups():
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")
+
+
+##########################################################################
+
def json_default_handler(obj):
'''Based on http://stackoverflow.com/a/2680060/1175849'''
# Handle datestamps