about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPjotr Prins2018-02-15 08:44:57 +0000
committerPjotr Prins2018-02-15 08:44:57 +0000
commitd636315ad629cb56ea3e697924160432f6132792 (patch)
tree476ed4196409a1dc918a41ca8d86400863eab875
parent2602be69f2869de376d1b9ced6131d880e9476c2 (diff)
parent1defefd05d0eb658fb5922fc755547261a5e914a (diff)
downloadgenenetwork2-d636315ad629cb56ea3e697924160432f6132792.tar.gz
Fix conflict
-rwxr-xr-xbin/genenetwork213
-rw-r--r--wqflask/tests/__init__.py0
-rw-r--r--wqflask/tests/es_double.py30
-rw-r--r--wqflask/tests/test_registration.py113
-rw-r--r--wqflask/utility/elasticsearch_tools.py46
-rw-r--r--wqflask/utility/tools.py27
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html41
-rw-r--r--wqflask/wqflask/user_manager.py309
8 files changed, 487 insertions, 92 deletions
diff --git a/bin/genenetwork2 b/bin/genenetwork2
index 8886e4bc..5438c1c0 100755
--- a/bin/genenetwork2
+++ b/bin/genenetwork2
@@ -95,6 +95,11 @@ export WQFLASK_OVERRIDES=$overrides   # JSON
 echo WQFLASK_SETTINGS=$settings
 echo WQFLASK_OVERRIDES=$overrides
 
+if [ -z $ELASTICSEARCH_PROFILE ]; then
+    echo -e "\033[1;33mWARNING: Elastic Search profile has not been set - use ELASTICSEARCH_PROFILE\033[0m";
+else
+    PYTHONPATH="$PYTHONPATH${PYTHONPATH:+:}$ELASTICSEARCH_PROFILE/lib/python2.7/site-packages"
+fi
 if [ -z $GN2_PROFILE ] ; then
     echo "WARNING: GN2_PROFILE has not been set - you need the environment, so I hope you know what you are doing!"
     export GN2_PROFILE=$(dirname $(dirname $(which genenetwork2)))
@@ -108,7 +113,7 @@ if [ -z $GN2_PROFILE ]; then
     read -p "PRESS [ENTER] TO CONTINUE..."
 else
     export PATH=$GN2_PROFILE/bin:$PATH
-    export PYTHONPATH=$GN2_PROFILE/lib/python2.7/site-packages
+    export PYTHONPATH="$GN2_PROFILE/lib/python2.7/site-packages${PYTHONPATH:+:}$PYTHONPATH"
     export R_LIBS_SITE=$GN2_PROFILE/site-library
     export GEM_PATH=$GN2_PROFILE/lib/ruby/gems/2.4.0
     export JS_GUIX_PATH=$GN2_PROFILE/share/genenetwork2/javascript
@@ -124,7 +129,11 @@ else
     if [ -z $GEMMA_WRAPPER_COMMAND ]; then
         export GEMMA_WRAPPER_COMMAND="$GN2_PROFILE/bin/gemma-wrapper"
     fi
-    if [ ! -d $PYTHONPATH ] ; then echo "PYTHONPATH not valid "$PYTHONPATH ; exit 1 ; fi
+    while IFS=":" read -ra PPATH; do
+	for PPart in "${PPATH[@]}"; do
+	    if [ ! -d $PPart ] ; then echo "PYTHONPATH not valid "$PYTHONPATH ; exit 1 ; fi
+	done
+    done <<< "$PYTHONPATH"
     if [ ! -d $R_LIBS_SITE ] ; then echo "R_LIBS_SITE not valid "$R_LIBS_SITE ; exit 1 ; fi
     if [ ! -d $GEM_PATH ] ; then echo "GEM_PATH not valid "$GEM_PATH ; exit 1 ; fi
 fi
diff --git a/wqflask/tests/__init__.py b/wqflask/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/wqflask/tests/__init__.py
diff --git a/wqflask/tests/es_double.py b/wqflask/tests/es_double.py
new file mode 100644
index 00000000..00739016
--- /dev/null
+++ b/wqflask/tests/es_double.py
@@ -0,0 +1,30 @@
+class ESDouble(object):
+    def __init__(self):
+        self.items = {
+            "users": {
+                "local": []
+            }}
+
+    def ping(self):
+        return true
+
+    def create(self, index, doc_type, body, id):
+        item = {"id": id, "_source": body}
+        if not self.items.get("index", None):
+            self.items[index] = {doc_type: [item]}
+        else:
+            self.items[index][doc_type].append(item)
+
+    def search(self, index, doc_type, body):
+        d = body["query"]["match"]
+        column = [(key, d[key]) for key in d]
+
+        items = []
+        for thing in self.items[index][doc_type]:
+            if thing["_source"][column[0][0]] == column[0][1]:
+                items.append(thing)
+                break
+        return {
+            "hits": {
+                "hits": items
+            }}
diff --git a/wqflask/tests/test_registration.py b/wqflask/tests/test_registration.py
new file mode 100644
index 00000000..50a2a84c
--- /dev/null
+++ b/wqflask/tests/test_registration.py
@@ -0,0 +1,113 @@
+import unittest
+import es_double
+import wqflask.user_manager
+from wqflask.user_manager import RegisterUser
+
+class TestRegisterUser(unittest.TestCase):
+    def setUp(self):
+        # Mock elasticsearch
+        self.es = es_double.ESDouble()
+
+        # Patch method
+        wqflask.user_manager.basic_info = lambda : {"basic_info": "some info"}
+
+    def tearDown(self):
+        self.es = None
+
+    def testRegisterUserWithNoData(self):
+        data = {}
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Data was not provided. Error was expected")
+
+    def testRegisterUserWithNoEmail(self):
+        data = {
+            "email_address": ""
+            , "full_name": "A.N. Other"
+            , "organization": "Some Organisation"
+            , "password": "testing"
+            , "password_confirm": "testing"
+            , "es_connection": self.es
+        }
+
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Email not provided. Error was expected")
+
+    def testRegisterUserWithNoName(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": ""
+            , "organization": "Some Organisation"
+            , "password": "testing"
+            , "password_confirm": "testing"
+            , "es_connection": self.es
+        }
+
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Name not provided. Error was expected")
+
+    def testRegisterUserWithNoOrganisation(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": "A.N. Other"
+            , "organization": ""
+            , "password": "testing"
+            , "password_confirm": "testing"
+            , "es_connection": self.es
+        }
+        
+        result = RegisterUser(data)
+        self.assertEqual(len(result.errors), 0, "Organisation not provided. Error not expected")
+
+    def testRegisterUserWithShortOrganisation(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": "A.N. Other"
+            , "organization": "SO"
+            , "password": "testing"
+            , "password_confirm": "testing"
+            , "es_connection": self.es
+        }
+        
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Organisation name too short. Error expected")
+
+    def testRegisterUserWithNoPassword(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": "A.N. Other"
+            , "organization": "Some Organisation"
+            , "password": None
+            , "password_confirm": None
+            , "es_connection": self.es
+        }
+
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Password not provided. Error was expected")
+
+    def testRegisterUserWithNonMatchingPasswords(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": "A.N. Other"
+            , "organization": "Some Organisation"
+            , "password": "testing"
+            , "password_confirm": "stilltesting"
+            , "es_connection": self.es
+        }
+
+        result = RegisterUser(data)
+        self.assertNotEqual(len(result.errors), 0, "Password mismatch. Error was expected")
+
+    def testRegisterUserWithCorrectData(self):
+        data = {
+            "email_address": "user@example.com"
+            , "full_name": "A.N. Other"
+            , "organization": "Some Organisation"
+            , "password": "testing"
+            , "password_confirm": "testing"
+            , "es_connection": self.es
+        }
+        result = RegisterUser(data)
+        self.assertEqual(len(result.errors), 0, "All data items provided. Errors were not expected")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/wqflask/utility/elasticsearch_tools.py b/wqflask/utility/elasticsearch_tools.py
new file mode 100644
index 00000000..a964b025
--- /dev/null
+++ b/wqflask/utility/elasticsearch_tools.py
@@ -0,0 +1,46 @@
+from elasticsearch import Elasticsearch, TransportError
+import logging
+
+def get_elasticsearch_connection():
+    es = None
+    try:
+        from utility.tools import ELASTICSEARCH_HOST, ELASTICSEARCH_PORT
+
+        es = Elasticsearch([{
+            "host": ELASTICSEARCH_HOST
+            , "port": ELASTICSEARCH_PORT
+        }]) if (ELASTICSEARCH_HOST and ELASTICSEARCH_PORT) else None
+
+        es_logger = logging.getLogger("elasticsearch")
+        es_logger.setLevel(logging.INFO)
+        es_logger.addHandler(logging.NullHandler())
+    except:
+        es = None
+
+    return es
+
+def get_user_by_unique_column(es, column_name, column_value, index="users", doc_type="local"):
+    return get_item_by_unique_column(es, column_name, column_value, index=index, doc_type=doc_type)
+
+def save_user(es, user, user_id):
+    es_save_data(es, "users", "local", user, user_id)
+
+def get_item_by_unique_column(es, column_name, column_value, index, doc_type):
+    item_details = None
+    try:
+        response = es.search(
+            index = index
+            , doc_type = doc_type
+            , body = { 
+                "query": { "match": { column_name: column_value } } 
+            })
+        if len(response["hits"]["hits"]) > 0:
+            item_details = response["hits"]["hits"][0]["_source"]
+    except TransportError as te: 
+        pass
+    return item_details
+
+def es_save_data(es, index, doc_type, data_item, data_id,):
+    from time import sleep
+    es.create(index, doc_type, body=data_item, id=data_id)
+    sleep(1) # Delay 1 second to allow indexing
diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py
index d3113302..005f9b0f 100644
--- a/wqflask/utility/tools.py
+++ b/wqflask/utility/tools.py
@@ -251,6 +251,33 @@ assert_dir(JS_GUIX_PATH)
 JS_GN_PATH         = get_setting('JS_GN_PATH')
 # assert_dir(JS_GN_PATH)
 
+def get_setting_safe(setting):
+    try:
+        return get_setting(setting)
+    except:
+        print("Could not find the setting '", setting, "'. Continuing with value unset")
+        return None
+
+GITHUB_CLIENT_ID = get_setting_safe('GITHUB_CLIENT_ID')
+GITHUB_CLIENT_SECRET = get_setting_safe('GITHUB_CLIENT_SECRET')
+GITHUB_AUTH_URL = None
+if GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET:
+    GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize?client_id="+GITHUB_CLIENT_ID+"&client_secret="+GITHUB_CLIENT_SECRET
+GITHUB_API_URL = get_setting_safe('GITHUB_API_URL')
+ORCID_CLIENT_ID = get_setting_safe('ORCID_CLIENT_ID')
+ORCID_CLIENT_SECRET = get_setting_safe('ORCID_CLIENT_SECRET')
+ORCID_AUTH_URL = None
+if ORCID_CLIENT_ID and ORCID_CLIENT_SECRET:
+    ORCID_AUTH_URL = "https://sandbox.orcid.org/oauth/authorize?response_type=code&scope=/authenticate&show_login=true&client_id="+ORCID_CLIENT_ID+"&client_secret="+ORCID_CLIENT_SECRET
+ORCID_TOKEN_URL = get_setting_safe('ORCID_TOKEN_URL')
+
+ELASTICSEARCH_HOST = get_setting_safe('ELASTICSEARCH_HOST')
+ELASTICSEARCH_PORT = get_setting_safe('ELASTICSEARCH_PORT')
+
+SMTP_CONNECT = get_setting_safe('SMTP_CONNECT')
+SMTP_USERNAME = get_setting_safe('SMTP_USERNAME')
+SMTP_PASSWORD = get_setting_safe('SMTP_PASSWORD')
+
 PYLMM_COMMAND      = app_set("PYLMM_COMMAND",pylmm_command())
 GEMMA_COMMAND      = app_set("GEMMA_COMMAND",gemma_command())
 assert(GEMMA_COMMAND is not None)
diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html
index b9f49a61..0dae3503 100644
--- a/wqflask/wqflask/templates/new_security/login_user.html
+++ b/wqflask/wqflask/templates/new_security/login_user.html
@@ -16,14 +16,39 @@
             <h4>Don't have an account?</h4>
 
 
-            <a href="/n/register" class="btn btn-primary modalize">Create a new account</a>
-
-
-            <hr />
+            {% if es_server: %}
+	    <a href="/n/register" class="btn btn-primary modalize">Create a new account</a>
+	    {% else: %}
+	    <div class="alert alert-warning">
+	      <p>You cannot create an account at this moment.<br />
+		Please try again later.</p>
+	    </div>
+	    {% endif %}
+
+	    <hr />
+	    <h4>Login with external services</h4>
+
+	    {% if external_login: %}
+	    <div>
+	      {% if external_login["github"]: %}
+	      <a href="{{external_login['github']}}" title="Login with GitHub" class="btn btn-info btn-group">Login with Github</a>
+	      {% endif %}
+
+	      {% if external_login["orcid"]: %}
+	      <a href="{{external_login['orcid']}}" title="Login with ORCID" class="btn btn-info btn-group">Login with ORCID</a>
+	      {% endif %}
+	    </div>
+	    {% else: %}
+	    <div class="alert alert-warning">
+	      <p>You cannot login with external services at this time.<br />
+		Please try again later.</p>
+	    </div>
+	    {% endif %}
+	    <hr />
 
             <h4>Already have an account? Sign in here.</h4>
 
-
+	    {% if es_server: %}
             <form class="form-horizontal" action="/n/login" method="POST" name="login_user_form" id="loginUserForm">
                  <fieldset>
                     <div class="form-group">
@@ -61,6 +86,12 @@
               </fieldset>
 
             </form>
+	    {% else: %}
+	    <div class="alert alert-warning">
+	      <p>You cannot login at this moment using your GeneNetwork account.<br />
+		Please try again later.</p>
+	    </div>
+	    {% endif %}
         </div>
     </div>
 
diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py
index f7fcd2d0..6b667615 100644
--- a/wqflask/wqflask/user_manager.py
+++ b/wqflask/wqflask/user_manager.py
@@ -54,6 +54,10 @@ logger = getLogger(__name__)
 
 from base.data_set import create_datasets_list
 
+import requests
+from utility.elasticsearch_tools import *
+
+es = get_elasticsearch_connection()
 THREE_DAYS = 60 * 60 * 24 * 3
 #THREE_DAYS = 45
 
@@ -268,16 +272,24 @@ class RegisterUser(object):
         self.thank_you_mode = False
         self.errors = []
         self.user = Bunch()
+        es = kw.get('es_connection', None)
+
+        if not es:
+            self.errors.append("Missing connection object")
 
-        self.user.email_address = kw.get('email_address', '').strip()
+        self.user.email_address = kw.get('email_address', '').encode("utf-8").strip()
         if not (5 <= len(self.user.email_address) <= 50):
             self.errors.append('Email Address needs to be between 5 and 50 characters.')
+        else:
+            email_exists = get_user_by_unique_column(es, "email_address", self.user.email_address)
+            if email_exists:
+                self.errors.append('User already exists with that email')
 
-        self.user.full_name = kw.get('full_name', '').strip()
+        self.user.full_name = kw.get('full_name', '').encode("utf-8").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()
+        self.user.organization = kw.get('organization', '').encode("utf-8").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.')
 
@@ -294,28 +306,11 @@ class RegisterUser(object):
         logger.debug("No errors!")
 
         set_password(password, self.user)
+        self.user.user_id = str(uuid.uuid4())
+        self.user.confirmed = 1
 
         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)
-
-        try:
-            db_session.commit()
-        except sqlalchemy.exc.IntegrityError:
-            # This exception is thrown if the email address is already in the database
-            # To do: Perhaps put a link to sign in using an existing account here
-            self.errors.append("An account with this email address already exists. "
-                               "Click the button above to sign in using an existing account.")
-            return
-
-        logger.debug("Adding verification email to queue")
-        #self.send_email_verification()
-        VerificationEmail(self.new_user)
-        logger.debug("Added verification email to queue")
-
-        self.thank_you_mode = True
-
+        save_user(es, self.user.__dict__, self.user.user_id)
 
 def set_password(password, user):
     pwfields = Bunch()
@@ -361,7 +356,7 @@ class VerificationEmail(object):
         verification_code = str(uuid.uuid4())
         key = self.key_prefix + ":" + verification_code
 
-        data = json.dumps(dict(id=user.id,
+        data = json.dumps(dict(id=user.user_id,
                                timestamp=timestamp())
                           )
 
@@ -378,23 +373,33 @@ class ForgotPasswordEmail(VerificationEmail):
     template_name = "email/forgot_password.txt"
     key_prefix = "forgot_password_code"
     subject = "GeneNetwork password reset"
+    fromaddr = "no-reply@genenetwork.org"
 
-    def __init__(self, user):
+    def __init__(self, toaddr):
+        from email.MIMEMultipart import MIMEMultipart
+        from email.MIMEText import MIMEText
         verification_code = str(uuid.uuid4())
         key = self.key_prefix + ":" + verification_code
 
-        data = json.dumps(dict(id=user.id,
-                               timestamp=timestamp())
-                          )
+        data = {
+            "verification_code": verification_code,
+            "email_address": toaddr,
+            "timestamp": timestamp()
+        }
+        es_save_data(es, self.key_prefix, "local", data, verification_code)
 
-        Redis.set(key, data)
-        #two_days = 60 * 60 * 24 * 2
-        Redis.expire(key, THREE_DAYS)
-        to = user.email_address
         subject = self.subject
-        body = render_template(self.template_name,
-                               verification_code = verification_code)
-        send_email(to, subject, body)
+        body = render_template(
+            self.template_name,
+            verification_code = verification_code)
+
+        msg = MIMEMultipart()
+        msg["To"] = toaddr
+        msg["Subject"] = self.subject
+        msg["From"] = self.fromaddr
+        msg.attach(MIMEText(body, "plain"))
+
+        send_email(toaddr, msg.as_string())
 
 
 class Password(object):
@@ -429,38 +434,60 @@ def verify_email():
     response.set_cookie(UserSession.cookie_name, session_id_signed)
     return response
 
-@app.route("/n/password_reset")
+@app.route("/n/password_reset", methods=['GET'])
 def password_reset():
     logger.debug("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)
+    # user_encode = DecodeUser(ForgotPasswordEmail.key_prefix).reencode_standalone()
+    verification_code = request.args.get('code')
+    hmac = request.args.get('hm')
+    if verification_code:
+        code_details = get_item_by_unique_column(
+            es
+            , "verification_code"
+            , verification_code
+            , ForgotPasswordEmail.key_prefix
+            , "local")
+        if code_details:
+            user_details = get_user_by_unique_column(
+                es
+                , "email_address"
+                , code_details["email_address"])
+            if user_details:
+                return render_template(
+                    "new_security/password_reset.html", user_encode=user_details["user_id"])
+            else:
+                flash("Invalid code: User no longer exists!", "error")
+        else:
+            flash("Invalid code: Password reset code does not exist or might have expired!", "error")
+        return redirect(url_for("login"))#render_template("new_security/login_user.html", error=error)
 
 @app.route("/n/password_reset_step2", methods=('POST',))
 def password_reset_step2():
+    from utility.elasticsearch_tools import es
     logger.debug("in password_reset request.url is:", request.url)
 
     errors = []
+    user_id = request.form['user_encode']
 
-    user_encode = request.form['user_encode']
-    verification_code, separator, hmac = user_encode.partition(':')
-
-    hmac_verified = actual_hmac_creation(verification_code)
     logger.debug("locals are:", locals())
 
 
-    assert hmac == hmac_verified, "Someone has been naughty"
-
-    user = DecodeUser.actual_get_user(ForgotPasswordEmail.key_prefix, verification_code)
-    logger.debug("user is:", user)
-
+    user = Bunch()
     password = request.form['password']
-
     set_password(password, user)
-    db_session.commit()
+
+    es.update(
+        index = "users"
+        , doc_type = "local"
+        , id = user_id
+        , body = {
+            "doc": {
+                "password": user.__dict__.get("password")
+            }
+        })
 
     flash("Password changed successfully. You can now sign in.", "alert-info")
     response = make_response(redirect(url_for('login')))
@@ -492,8 +519,83 @@ class DecodeUser(object):
 @app.route("/n/login", methods=('GET', 'POST'))
 def login():
     lu = LoginUser()
-    return lu.standard_login()
+    login_type = request.args.get("type")
+    if login_type:
+        uid = request.args.get("uid")
+        return lu.oauth2_login(login_type, uid)
+    else:
+        return lu.standard_login()
+
+@app.route("/n/login/github_oauth2", methods=('GET', 'POST'))
+def github_oauth2():
+    from utility.tools import GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
+    code = request.args.get("code")
+    data = {
+        "client_id": GITHUB_CLIENT_ID,
+        "client_secret": GITHUB_CLIENT_SECRET,
+        "code": code
+    }
+    result = requests.post("https://github.com/login/oauth/access_token", json=data)
+    result_dict = {arr[0]:arr[1] for arr in [tok.split("=") for tok in [token.encode("utf-8") for token in result.text.split("&")]]}
+
+    github_user = get_github_user_details(result_dict["access_token"])
+    user_details = get_user_by_unique_column(es, "github_id", github_user["id"])
+    if user_details == None:
+        user_details = {
+            "user_id": str(uuid.uuid4())
+            , "name": github_user["name"].encode("utf-8")
+            , "github_id": github_user["id"]
+            , "user_url": github_user["html_url"].encode("utf-8")
+            , "login_type": "github"
+            , "organization": ""
+            , "active": 1
+            , "confirmed": 1
+        }
+        save_user(es, user_details, user_details["user_id"])
+    url = "/n/login?type=github&uid="+user_details["user_id"]
+    return redirect(url)
+
+@app.route("/n/login/orcid_oauth2", methods=('GET', 'POST'))
+def orcid_oauth2():
+    from uuid import uuid4
+    from utility.tools import ORCID_CLIENT_ID, ORCID_CLIENT_SECRET, ORCID_TOKEN_URL, ORCID_AUTH_URL
+    code = request.args.get("code")
+    error = request.args.get("error")
+    url = "/n/login"
+    if code:
+        data = {
+            "client_id": ORCID_CLIENT_ID
+            , "client_secret": ORCID_CLIENT_SECRET
+            , "grant_type": "authorization_code"
+            , "code": code
+        }
+        result = requests.post(ORCID_TOKEN_URL, data=data)
+        result_dict = json.loads(result.text.encode("utf-8"))
+
+        user_details = get_user_by_unique_column(es, "orcid", result_dict["orcid"])
+        if user_details == None:
+            user_details = {
+                "user_id": str(uuid4())
+                , "name": result_dict["name"]
+                , "orcid": result_dict["orcid"]
+                , "user_url": "%s/%s" % (
+                    "/".join(ORCID_AUTH_URL.split("/")[:-2]),
+                    result_dict["orcid"])
+                , "login_type": "orcid"
+                , "organization": ""
+                , "active": 1
+                , "confirmed": 1
+            }
+            save_user(es, user_details, user_details["user_id"])
+        url = "/n/login?type=orcid&uid="+user_details["user_id"]
+    else:
+        flash("There was an error getting code from ORCID")
+    return redirect(url)
 
+def get_github_user_details(access_token):
+    from utility.tools import GITHUB_API_URL
+    result = requests.get(GITHUB_API_URL, params={"access_token":access_token})
+    return result.json()
 
 class LoginUser(object):
     remember_time = 60 * 60 * 24 * 30 # One month in seconds
@@ -501,27 +603,54 @@ class LoginUser(object):
     def __init__(self):
         self.remember_me = False
 
+    def oauth2_login(self, login_type, user_id):
+        """Login via an OAuth2 provider"""
+        user_details = get_user_by_unique_column(es, "user_id", user_id)
+        if user_details:
+            user = model.User()
+            user.id = user_details["user_id"]
+            user.full_name = user_details["name"]
+            user.login_type = user_details["login_type"]
+            return self.actual_login(user)
+        else:
+            flash("Error logging in via OAuth2")
+            return make_response(redirect(url_for('login')))
+
     def standard_login(self):
         """Login through the normal form"""
         params = request.form if request.form else request.args
         logger.debug("in login params are:", params)
         if not params:
-            return render_template("new_security/login_user.html")
+            from utility.tools import GITHUB_AUTH_URL, ORCID_AUTH_URL
+            external_login = None
+            if GITHUB_AUTH_URL or ORCID_AUTH_URL:
+                external_login={
+                    "github": GITHUB_AUTH_URL,
+                    "orcid": ORCID_AUTH_URL
+                }
+            return render_template(
+                "new_security/login_user.html"
+                , external_login=external_login
+                , es_server=es.ping())
         else:
-            try:
-                user = model.User.query.filter_by(email_address=params['email_address']).one()
-            except sqlalchemy.orm.exc.NoResultFound:
-                logger.debug("No account exists for that email address")
-                valid = False
-                user = None
-            else:
+            user_details = get_user_by_unique_column(es, "email_address", params["email_address"])
+            user = None
+            valid = None
+            if user_details:
+                user = model.User();
+                for key in user_details:
+                    user.__dict__[key] = user_details[key]
+                print("RETRIEVED USER: ", user)
+                valid = False;
+
                 submitted_password = params['password']
                 pwfields = Struct(json.loads(user.password))
-                encrypted = Password(submitted_password,
-                                              pwfields.salt,
-                                              pwfields.iterations,
-                                              pwfields.keylength,
-                                              pwfields.hashfunc)
+                encrypted = Password(
+                    submitted_password,
+                    pwfields.salt,
+                    pwfields.iterations,
+                    pwfields.keylength,
+                    pwfields.hashfunc)
                 logger.debug("\n\nComparing:\n{}\n{}\n".format(encrypted.password, pwfields.password))
                 valid = pbkdf2.safe_str_cmp(encrypted.password, pwfields.password)
                 logger.debug("valid is:", valid)
@@ -549,7 +678,7 @@ class LoginUser(object):
         else:
             if user:
                 self.unsuccessful_login(user)
-            flash("Invalid email-address or password. Please try again.", "alert-error")
+            flash("Invalid email-address or password. Please try again.", "alert-danger")
             response = make_response(redirect(url_for('login')))
 
             return response
@@ -589,8 +718,6 @@ class LoginUser(object):
         else:
             expire_time = THREE_DAYS
         Redis.expire(key, expire_time)
-        db_session.add(login_rec)
-        db_session.commit()
         return session_id_signed
 
     def unsuccessful_login(self, user):
@@ -618,13 +745,16 @@ def forgot_password():
 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)
+    user_details = get_user_by_unique_column(es, "email_address", email_address)
+    if user_details:
+        ForgotPasswordEmail(user_details["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)
 
@@ -691,16 +821,17 @@ def register():
 
 
     params = request.form if request.form else request.args
+    params = params.to_dict(flat=True)
+    params["es_connection"] = es
 
     if params:
         logger.debug("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)
+        if len(errors) == 0:
+            flash("Registration successful. You may login with your new account", "alert-info")
+            return redirect(url_for("login"))
 
     return render_template("new_security/register_user.html", values=params, errors=errors)
 
@@ -771,13 +902,21 @@ 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 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 send_email(toaddr, msg, fromaddr="no-reply@genenetwork.org"):
+    from smtplib import SMTP
+    from utility.tools import SMTP_CONNECT, SMTP_USERNAME, SMTP_PASSWORD
+    server = SMTP(SMTP_CONNECT)
+    server.starttls()
+    server.login(SMTP_USERNAME, SMTP_PASSWORD)
+    server.sendmail(fromaddr, toaddr, msg)
+    server.quit()