diff options
author | Pjotr Prins | 2018-03-19 10:52:28 +0100 |
---|---|---|
committer | GitHub | 2018-03-19 10:52:28 +0100 |
commit | b359cb0712e9ef923d35524f310d841581d48f30 (patch) | |
tree | 3c4cafc0bc0b481490674c807f279b4371130d0c | |
parent | 2602be69f2869de376d1b9ced6131d880e9476c2 (diff) | |
parent | 5ccf077f53c6546bb9258c7116b4b1cf8903375f (diff) | |
download | genenetwork2-b359cb0712e9ef923d35524f310d841581d48f30.tar.gz |
Merge pull request #5 from fredmanglis/testing
Testing
23 files changed, 1013 insertions, 208 deletions
diff --git a/bin/genenetwork2 b/bin/genenetwork2 index 8886e4bc..2a83a1cd 100755 --- a/bin/genenetwork2 +++ b/bin/genenetwork2 @@ -95,6 +95,13 @@ export WQFLASK_OVERRIDES=$overrides # JSON echo WQFLASK_SETTINGS=$settings echo WQFLASK_OVERRIDES=$overrides +# This is a temporary hack to inject ES - should have added python2-elasticsearch package to guix instead +# if [ -z $ELASTICSEARCH_PROFILE ]; then +# echo -e "WARNING: Elastic Search profile has not been set - use ELASTICSEARCH_PROFILE"; +# 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 +115,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 +131,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 @@ -157,8 +168,9 @@ if [ "$1" = '-c' ] ; then cd $GN2_BASE_DIR/wqflask cmd=${2#wqflask/} echo PYTHONPATH=$PYTHONPATH - echo RUNNING COMMAND $cmd - python $cmd + shift ; shift + echo RUNNING COMMAND $cmd $* + python $cmd $* exit $? fi # Now handle command parameter -cli which runs in bash @@ -167,8 +179,9 @@ if [ "$1" = "-cli" ] ; then cd $GN2_BASE_DIR/wqflask cmd=$2 echo PYTHONPATH=$PYTHONPATH - echo RUNNING COMMAND $cmd - $cmd + shift ; shift + echo RUNNING COMMAND $cmd $* + $cmd $* exit $? fi if [ "$1" = '-gunicorn' ] ; then diff --git a/bin/mechnical-rob b/bin/mechnical-rob new file mode 100755 index 00000000..be223d94 --- /dev/null +++ b/bin/mechnical-rob @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby + + +USAGE = <<EOT +This is Mechanical-Rob - an automated web server tester for + Genenetwork.org that uses the brilliant + mechanize gem with minitest. + +To use this software you need to install mechanize. Run it from +the root of the genenetwork2 source tree as, for example, + + ./bin/test-website http://localhost:5003/ (default) + +If you are using the small deployment database you can use + + ./bin/test-website --skip -n + +To run all tests + + ./bin/test-website --all + +To run individual tests on localhost you can do + + ruby -Itest -Itest/lib test/lib/mapping.rb --name="/Mapping/" + +For more information see http://genenetwork.org/ + +EOT +$stderr.print USAGE + +require 'optparse' + +options = { database: :small, link_checker: false} +opts = OptionParser.new do |o| + o.banner = "Usage: #{File.basename($0)} [options] URL" + + o.on('-d','--database', String, 'Use database (default db_webqtl_s)') do |s| + options[:database] = + case s + when 'xx' + :unknown + else + :small + end + end + + o.on('--all', 'Run all tests') do + options[:all] = true + end + + o.on('-l','--link-checker', 'Check for dead links') do + options[:link_checker] = true + end + + o.on('--navigation', 'Check for navigation') do + options[:navigation] = true + end + + o.on('--mapping', 'Check for mapping') do + options[:mapping] = true + end + + o.on('--skip-broken', 'Skip tests that are known to be broken') do + options[:skip_broken] = true + end + + o.separator "" + o.on_tail('-h', '--help', 'display this help and exit') do + options[:show_help] = true + end +end + +opts.parse!(ARGV) + +if options[:show_help] + print opts + exit 1 +end + +$options = options # we are using a global here +$host = + if ARGV.size>0 + ARGV.shift + else + "http://localhost:5003" + end + +$stderr.print "Testing <",$host,">\n" + +require 'mechanize' +require 'minitest/spec' +require 'minitest/autorun' + +# These are the actual testing modules + +libpath = File.dirname(File.dirname(__FILE__)) +$: << File.join(libpath,'test/lib') + +require 'main_web_functionality' + +if options[:all] or options[:mapping] + require 'mapping' +end + +if options[:all] or options[:link_checker] + require 'link_checker' +end + +if options[:all] or options[:navigation] + require 'navigation' +end diff --git a/bin/test-website b/bin/test-website index be223d94..5935f016 100755 --- a/bin/test-website +++ b/bin/test-website @@ -1,111 +1,7 @@ -#!/usr/bin/env ruby +#! /bin/bash - -USAGE = <<EOT -This is Mechanical-Rob - an automated web server tester for - Genenetwork.org that uses the brilliant - mechanize gem with minitest. - -To use this software you need to install mechanize. Run it from -the root of the genenetwork2 source tree as, for example, - - ./bin/test-website http://localhost:5003/ (default) - -If you are using the small deployment database you can use - - ./bin/test-website --skip -n - -To run all tests - - ./bin/test-website --all - -To run individual tests on localhost you can do - - ruby -Itest -Itest/lib test/lib/mapping.rb --name="/Mapping/" - -For more information see http://genenetwork.org/ - -EOT -$stderr.print USAGE - -require 'optparse' - -options = { database: :small, link_checker: false} -opts = OptionParser.new do |o| - o.banner = "Usage: #{File.basename($0)} [options] URL" - - o.on('-d','--database', String, 'Use database (default db_webqtl_s)') do |s| - options[:database] = - case s - when 'xx' - :unknown - else - :small - end - end - - o.on('--all', 'Run all tests') do - options[:all] = true - end - - o.on('-l','--link-checker', 'Check for dead links') do - options[:link_checker] = true - end - - o.on('--navigation', 'Check for navigation') do - options[:navigation] = true - end - - o.on('--mapping', 'Check for mapping') do - options[:mapping] = true - end - - o.on('--skip-broken', 'Skip tests that are known to be broken') do - options[:skip_broken] = true - end - - o.separator "" - o.on_tail('-h', '--help', 'display this help and exit') do - options[:show_help] = true - end -end - -opts.parse!(ARGV) - -if options[:show_help] - print opts +if [ -z $GN2_PROFILE ]; then + echo "Run request tests with something like" + echo env GN2_PROFILE=/home/wrk/opt/gn-latest ./bin/genenetwork2 ./etc/default_settings.py -c ../test/requests/test-website.py http://localhost:5003 exit 1 -end - -$options = options # we are using a global here -$host = - if ARGV.size>0 - ARGV.shift - else - "http://localhost:5003" - end - -$stderr.print "Testing <",$host,">\n" - -require 'mechanize' -require 'minitest/spec' -require 'minitest/autorun' - -# These are the actual testing modules - -libpath = File.dirname(File.dirname(__FILE__)) -$: << File.join(libpath,'test/lib') - -require 'main_web_functionality' - -if options[:all] or options[:mapping] - require 'mapping' -end - -if options[:all] or options[:link_checker] - require 'link_checker' -end - -if options[:all] or options[:navigation] - require 'navigation' -end +fi diff --git a/etc/default_settings.py b/etc/default_settings.py index 699d21f1..a70d8aec 100644 --- a/etc/default_settings.py +++ b/etc/default_settings.py @@ -41,6 +41,24 @@ SECURITY_POST_LOGIN_VIEW = "/thank_you" SERVER_PORT = 5003 # running on localhost SECRET_HMAC_CODE = '\x08\xdf\xfa\x93N\x80\xd9\\H@\\\x9f`\x98d^\xb4a;\xc6OM\x946a\xbc\xfc\x80:*\xebc' +GITHUB_CLIENT_ID = "UNKNOWN" +GITHUB_CLIENT_SECRET = "UNKNOWN" +GITHUB_AUTH_URL = "UNKNOWN" +GITHUB_API_URL = "UNKNOWN" + +ORCID_CLIENT_ID = "UNKNOWN" +ORCID_CLIENT_SECRET = "UNKNOWN" +ORCID_AUTH_URL = "UNKNOWN" +ORCID_TOKEN_URL = "UNKNOWN" + +ELASTICSEARCH_HOST = "localhost" +ELASTICSEARCH_PORT = '9200' + +SMTP_CONNECT = "UNKNOWN" +SMTP_USERNAME = "UNKNOWN" +SMTP_PASSWORD = "UNKNOWN" + + # ---- Behavioural settings (defaults) note that logger and log levels can # be overridden at the module level and with enviroment settings WEBSERVER_MODE = 'DEV' # Python webserver mode (DEBUG|DEV|PROD) diff --git a/test/requests/link_checker.py b/test/requests/link_checker.py new file mode 100644 index 00000000..256bf6ef --- /dev/null +++ b/test/requests/link_checker.py @@ -0,0 +1,63 @@ +from __future__ import print_function +import re +import requests +from lxml.html import parse +from requests.exceptions import ConnectionError + +def is_root_link(link): + pattern = re.compile("^/$") + return pattern.match(link) + +def is_mailto_link(link): + pattern = re.compile("^mailto:.*") + return pattern.match(link) + +def is_internal_link(link): + pattern = re.compile("^/.*") + return pattern.match(link) + +def get_links(doc): + return filter( + lambda x: not ( + is_root_link(x) + or is_mailto_link(x)) + , map(lambda y: y.get("href") + , doc.cssselect("a"))) + +def verify_link(link): + try: + result = requests.get(link, timeout=20) + if result.status_code == 200: + print(link+" ==> OK") + else: + print("ERROR: link `"+link+"` failed with status " + , result.status_code) + except ConnectionError as ex: + print("ERROR: ", link, ex) + +def check_page(host, start_url): + print("") + print("Checking links in page `"+start_url+"`") + doc = parse(start_url).getroot() + links = get_links(doc) + internal_links = filter(is_internal_link, links) + external_links = filter(lambda x: not is_internal_link(x), links) + external_links.append("http://somenon-existentsite.brr") + for link in internal_links: + verify_link(host+link) + + for link in external_links: + verify_link(link) + +def check_links(args_obj, parser): + print("") + print("Checking links") + host = args_obj.host + + # Check the home page + check_page(host, host) + + # Check traits page + check_page( + host, + host+"/show_trait?trait_id=1435395_s_at&dataset=HC_M2_0606_P") diff --git a/test/requests/main_web_functionality.py b/test/requests/main_web_functionality.py new file mode 100644 index 00000000..7b89b833 --- /dev/null +++ b/test/requests/main_web_functionality.py @@ -0,0 +1,40 @@ +from __future__ import print_function +import re +import requests +from lxml.html import parse +from link_checker import check_page +from requests.exceptions import ConnectionError + +def check_home(url): + doc = parse(url).getroot() + search_button = doc.cssselect("#btsearch") + assert(search_button[0].value == "Search") + print("OK") + +def check_search_page(host): + data = dict( + species="mouse" + , group="BXD" + , type="Hippocampus mRNA" + , dataset="HC_M2_0606_P" + , search_terms_or="" + , search_terms_and="MEAN=(15 16) LRS=(23 46)") + result = requests.get(host+"/search", params=data) + found = result.text.find("/show_trait?trait_id=1435395_s_at&dataset=HC_M2_0606_P") + assert(found >= 0) + print("OK") + check_traits_page(host, "/show_trait?trait_id=1435395_s_at&dataset=HC_M2_0606_P") + +def check_traits_page(host, traits_url): + doc = parse(host+traits_url).getroot() + traits_form = doc.forms[1] + assert(traits_form.fields["corr_dataset"] == "HC_M2_0606_P") + print("OK") + check_page(host, host+traits_url) + +def check_main_web_functionality(args_obj, parser): + print("") + print("Checking main web functionality...") + host = args_obj.host + check_home(host) + check_search_page(host) diff --git a/test/requests/mapping_tests.py b/test/requests/mapping_tests.py new file mode 100644 index 00000000..fd20df11 --- /dev/null +++ b/test/requests/mapping_tests.py @@ -0,0 +1,43 @@ +from __future__ import print_function +import re +import json +import requests +from lxml.html import fromstring + +def get_data(list_item): + try: + value = list_item[1] + except: + value = None + #print("list_item:", list_item, "==>", value) + return value + +def load_data_from_file(): + filename = "../test/data/input/mapping/1435395_s_at_HC_M2_0606_P.json" + file_handle = open(filename, "r") + file_data = json.loads(file_handle.read().encode("utf-8")) + return file_data + +def check_pylmm_tool_selection(host, data): + data["method"] = "pylmm" + page = requests.post(host+"/marker_regression", data=data) + doc = fromstring(page.text) + form = doc.forms[1] + assert form.fields["dataset"] == "HC_M2_0606_P" + assert form.fields["value:BXD1"] == "15.034" # Check value in the file + +def check_R_qtl_tool_selection(host, data): + pass + +def check_CIM_tool_selection(host, data): + pass + +def check_mapping(args_obj, parser): + print("") + print("Checking mapping") + + host = args_obj.host + data = load_data_from_file() + check_pylmm_tool_selection(host, data) + check_R_qtl_tool_selection(host, data) + check_CIM_tool_selection(host, data) diff --git a/test/requests/parametrized_test.py b/test/requests/parametrized_test.py new file mode 100644 index 00000000..abf98fce --- /dev/null +++ b/test/requests/parametrized_test.py @@ -0,0 +1,27 @@ +import logging +import unittest +from elasticsearch import Elasticsearch, TransportError + +class ParametrizedTest(unittest.TestCase): + + def __init__(self, methodName='runTest', gn2_url="http://localhost:5003", es_url="localhost:9200"): + super(ParametrizedTest, self).__init__(methodName=methodName) + self.gn2_url = gn2_url + self.es_url = es_url + + def setUp(self): + self.es = Elasticsearch([self.es_url]) + self.es_cleanup = [] + + es_logger = logging.getLogger("elasticsearch") + es_logger.addHandler( + logging.FileHandler("/tmp/es_TestRegistrationInfo.log")) + es_trace_logger = logging.getLogger("elasticsearch.trace") + es_trace_logger.addHandler( + logging.FileHandler("/tmp/es_TestRegistrationTrace.log")) + + def tearDown(self): + self.es.delete_by_query( + index="users" + , doc_type="local" + , body={"query":{"match":{"email_address":"test@user.com"}}}) diff --git a/test/requests/run-integration-tests.py b/test/requests/run-integration-tests.py new file mode 100644 index 00000000..5e816549 --- /dev/null +++ b/test/requests/run-integration-tests.py @@ -0,0 +1,34 @@ +import sys +from test_login_local import TestLoginLocal +from test_login_orcid import TestLoginOrcid +from test_login_github import TestLoginGithub +from test_registration import TestRegistration +from unittest import TestSuite, TextTestRunner, TestLoader + +test_cases = [ + TestRegistration + , TestLoginLocal + , TestLoginGithub + , TestLoginOrcid +] + +def suite(gn2_url, es_url): + the_suite = TestSuite() + for case in test_cases: + the_suite.addTests(initTest(case, gn2_url, es_url)) + return the_suite + +def initTest(klass, gn2_url, es_url): + loader = TestLoader() + methodNames = loader.getTestCaseNames(klass) + return [klass(mname, gn2_url, es_url) for mname in methodNames] + +def main(gn2_url, es_url): + runner = TextTestRunner() + runner.run(suite(gn2_url, es_url)) + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise Exception("Required arguments missing:\n\tTry running `run-integration-test.py <gn2-url> <es-url>`") + else: + main(sys.argv[1], sys.argv[2]) diff --git a/test/requests/test-website.py b/test/requests/test-website.py new file mode 100755 index 00000000..2bef6eb1 --- /dev/null +++ b/test/requests/test-website.py @@ -0,0 +1,71 @@ +# Run with something like +# +# env GN2_PROFILE=/home/wrk/opt/gn-latest ./bin/genenetwork2 ./etc/default_settings.py -c ../test/requests/test-website.py http://localhost:5003 +# +# Mostly to pick up the Guix GN2_PROFILE and python modules +from __future__ import print_function +import argparse +from link_checker import check_links +from mapping_tests import check_mapping +from main_web_functionality import check_main_web_functionality + +print("Mechanical Rob firing up...") + +def run_all(args_obj, parser): + print("") + print("Running all tests.") + check_main_web_functionality(args_obj, parser) + check_links(args_obj, parser) + check_mapping(args_obj, parser) + # TODO: Add other functions as they are created. + +def print_help(args_obj, parser): + print(parser.format_help()) + +def dummy(args_obj, parser): + print("Not implemented yet.") + + +desc = """ +This is Mechanical-Rob - an automated web server tester for + Genenetwork.org +""" +parser = argparse.ArgumentParser(description=desc) + +parser.add_argument("-d", "--database", metavar="DB", type=str + , default="db_webqtl_s" + , help="Use database (default db_webqtl_s)") + +parser.add_argument("host", metavar="HOST", type=str + , default="http://localhost:5003" + , help="The url to the web server") + +parser.add_argument("-a", "--all", dest="accumulate", action="store_const" + , const=run_all, default=print_help + , help="Runs all tests.") + +parser.add_argument("-l", "--link-checker", dest="accumulate" + , action='store_const', const=check_links, default=print_help + , help="Checks for dead links.") + +parser.add_argument("-f", "--main-functionality", dest="accumulate" + , action='store_const', const=check_main_web_functionality + , default=print_help + , help="Checks for main web functionality.") + +parser.add_argument("-m", "--mapping", dest="accumulate" + , action="store_const", const=check_mapping, default=print_help + , help="Checks for mapping.") + +# parser.add_argument("-n", "--navigation", dest="accumulate" +# , action="store_const", const=check_navigation, default=print_help +# , help="Checks for navigation.") + +# parser.add_argument("-s", "--skip-broken", dest="accumulate" +# , action="store_const", const=dummy, default=print_help +# , help="Skip tests that are known to be broken.") + +args = parser.parse_args() +# print("The arguments object: ", args) + +args.accumulate(args, parser) diff --git a/test/requests/test_login_github.py b/test/requests/test_login_github.py new file mode 100644 index 00000000..1bf4f695 --- /dev/null +++ b/test/requests/test_login_github.py @@ -0,0 +1,47 @@ +import uuid +import requests +from time import sleep +from wqflask import app +from parameterized import parameterized +from parametrized_test import ParametrizedTest + +login_link_text = '<a id="login_in" href="/n/login">Sign in</a>' +logout_link_text = '<a id="login_out" title="Signed in as ." href="/n/logout">Sign out</a>' +uid = str(uuid.uuid4()) + +class TestLoginGithub(ParametrizedTest): + + def setUp(self): + super(TestLoginGithub, self).setUp() + data = { + "user_id": uid + , "name": "A. T. Est User" + , "github_id": 693024 + , "user_url": "https://fake-github.com/atestuser" + , "login_type": "github" + , "organization": "" + , "active": 1 + , "confirmed": 1 + } + self.es.create(index="users", doc_type="local", body=data, id=uid) + sleep(1) + + def tearDown(self): + super(TestLoginGithub, self).tearDown() + self.es.delete(index="users", doc_type="local", id=uid) + + def testLoginUrl(self): + login_button_text = '<a href="https://github.com/login/oauth/authorize?client_id=' + app.config.get("GITHUB_CLIENT_ID") + '&client_secret=' + app.config.get("GITHUB_CLIENT_SECRET") + '" title="Login with GitHub" class="btn btn-info btn-group">Login with Github</a>' + result = requests.get(self.gn2_url+"/n/login") + index = result.content.find(login_button_text) + self.assertTrue(index >= 0, "Should have found `Login with Github` button") + + @parameterized.expand([ + ("1234", login_link_text, "Login should have failed with non-existing user") + , (uid, logout_link_text, "Login should have been successful with existing user") + ]) + def testLogin(self, test_uid, expected, message): + url = self.gn2_url+"/n/login?type=github&uid="+test_uid + result = requests.get(url) + index = result.content.find(expected) + self.assertTrue(index >= 0, message) diff --git a/test/requests/test_login_local.py b/test/requests/test_login_local.py new file mode 100644 index 00000000..808649ca --- /dev/null +++ b/test/requests/test_login_local.py @@ -0,0 +1,60 @@ +import requests +from wqflask import user_manager +from parameterized import parameterized +from parametrized_test import ParametrizedTest + +login_link_text = '<a id="login_in" href="/n/login">Sign in</a>' +logout_link_text = '<a id="login_out" title="Signed in as ." href="/n/logout">Sign out</a>' + +class TestLoginLocal(ParametrizedTest): + + def setUp(self): + super(TestLoginLocal, self).setUp() + self.login_url = self.gn2_url +"/n/login" + data = { + "es_connection": self.es, + "email_address": "test@user.com", + "full_name": "Test User", + "organization": "Test Organisation", + "password": "test_password", + "password_confirm": "test_password" + } + user_manager.basic_info = lambda : { "basic_info": "basic" } + user_manager.RegisterUser(data) + + + @parameterized.expand([ + ( + { + "email_address": "non@existent.email", + "password": "doesitmatter?" + }, login_link_text, "Login should have failed with the wrong user details."), + ( + { + "email_address": "test@user.com", + "password": "test_password" + }, logout_link_text, "Login should have been successful with correct user details and neither import_collections nor remember_me set"), + ( + { + "email_address": "test@user.com", + "password": "test_password", + "import_collections": "y" + }, logout_link_text, "Login should have been successful with correct user details and only import_collections set"), + ( + { + "email_address": "test@user.com", + "password": "test_password", + "remember_me": "y" + }, logout_link_text, "Login should have been successful with correct user details and only remember_me set"), + ( + { + "email_address": "test@user.com", + "password": "test_password", + "remember_me": "y", + "import_collections": "y" + }, logout_link_text, "Login should have been successful with correct user details, and both remember_me, and import_collections set") + ]) + def testLogin(self, data, expected, message): + result = requests.post(self.login_url, data=data) + index = result.content.find(expected) + self.assertTrue(index >= 0, message) diff --git a/test/requests/test_login_orcid.py b/test/requests/test_login_orcid.py new file mode 100644 index 00000000..ea15642e --- /dev/null +++ b/test/requests/test_login_orcid.py @@ -0,0 +1,47 @@ +import uuid +import requests +from time import sleep +from wqflask import app +from parameterized import parameterized +from parametrized_test import ParametrizedTest + +login_link_text = '<a id="login_in" href="/n/login">Sign in</a>' +logout_link_text = '<a id="login_out" title="Signed in as ." href="/n/logout">Sign out</a>' +uid = str(uuid.uuid4()) + +class TestLoginOrcid(ParametrizedTest): + + def setUp(self): + super(TestLoginOrcid, self).setUp() + data = { + "user_id": uid + , "name": "A. T. Est User" + , "orcid": 345872 + , "user_url": "https://fake-orcid.org/atestuser" + , "login_type": "orcid" + , "organization": "" + , "active": 1 + , "confirmed": 1 + } + self.es.create(index="users", doc_type="local", body=data, id=uid) + sleep(1) + + def tearDown(self): + super(TestLoginOrcid, self).tearDown() + self.es.delete(index="users", doc_type="local", id=uid) + + def testLoginUrl(self): + login_button_text = 'a href="https://sandbox.orcid.org/oauth/authorize?response_type=code&scope=/authenticate&show_login=true&client_id=' + app.config.get("ORCID_CLIENT_ID") + '&client_secret=' + app.config.get("ORCID_CLIENT_SECRET") + '" title="Login with ORCID" class="btn btn-info btn-group">Login with ORCID</a>' + result = requests.get(self.gn2_url+"/n/login") + index = result.content.find(login_button_text) + self.assertTrue(index >= 0, "Should have found `Login with ORCID` button") + + @parameterized.expand([ + ("1234", login_link_text, "Login should have failed with non-existing user") + , (uid, logout_link_text, "Login should have been successful with existing user") + ]) + def testLogin(self, test_uid, expected, message): + url = self.gn2_url+"/n/login?type=orcid&uid="+test_uid + result = requests.get(url) + index = result.content.find(expected) + self.assertTrue(index >= 0, message) diff --git a/test/requests/test_registration.py b/test/requests/test_registration.py new file mode 100644 index 00000000..0047e8a6 --- /dev/null +++ b/test/requests/test_registration.py @@ -0,0 +1,41 @@ +import sys +import requests +from parametrized_test import ParametrizedTest + +class TestRegistration(ParametrizedTest): + + def tearDown(self): + for item in self.es_cleanup: + self.es.delete(index="users", doc_type="local", id=item["_id"]) + + def testRegistrationPage(self): + if self.es.ping(): + data = { + "email_address": "test@user.com", + "full_name": "Test User", + "organization": "Test Organisation", + "password": "test_password", + "password_confirm": "test_password" + } + requests.post(self.gn2_url+"/n/register", data) + response = self.es.search( + index="users" + , doc_type="local" + , body={ + "query": {"match": {"email_address": "test@user.com"}}}) + self.assertEqual(len(response["hits"]["hits"]), 1) + else: + self.skipTest("The elasticsearch server is down") + +def main(gn2, es): + import unittest + suite = unittest.TestSuite() + suite.addTest(TestRegistration(methodName="testRegistrationPage", gn2_url=gn2, es_url=es)) + runner = unittest.TextTestRunner() + runner.run(suite) + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise Exception("Required arguments missing") + else: + main(sys.argv[1], sys.argv[2]) diff --git a/test/unittest/test_registration.py b/test/unittest/test_registration.py new file mode 100644 index 00000000..98d0cdff --- /dev/null +++ b/test/unittest/test_registration.py @@ -0,0 +1,27 @@ +# Run test with something like +# +# env GN2_PROFILE=~/opt/gn-latest GENENETWORK_FILES=$HOME/gn2_data ./bin/genenetwork2 ./etc/default_settings.py -c ../test/unittest/test_registration.py +# + +import unittest +import mock.es_double as es +from wqflask.user_manager import RegisterUser + +class TestRegisterUser(unittest.TestCase): + def setUp(self): + self.es = es.ESDouble() + + 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, "Errors were not expected") + +if __name__ == "__main__": + unittest.main() diff --git a/wqflask/mock/__init__.py b/wqflask/mock/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/wqflask/mock/__init__.py diff --git a/wqflask/mock/es_double.py b/wqflask/mock/es_double.py new file mode 100644 index 00000000..6ef8a1b9 --- /dev/null +++ b/wqflask/mock/es_double.py @@ -0,0 +1,15 @@ +class ESDouble(object): + def __init__(self): + self.items = {} + + def ping(self): + return true + + def create(self, index, doc_type, body, id): + self.items["index"] = {doc_type: {"id": id, "_source": data}} + + def search(self, index, doc_type, body): + return { + "hits": { + "hits": self.items[index][doc_type][body] + }} diff --git a/wqflask/run_gunicorn.py b/wqflask/run_gunicorn.py index 14a2d689..ebe3add5 100644 --- a/wqflask/run_gunicorn.py +++ b/wqflask/run_gunicorn.py @@ -11,6 +11,9 @@ print "Starting up Gunicorn process" from wqflask import app +app.config['SESSION_TYPE'] = 'filesystem' +app.config['SECRET_KEY'] = 'super secret key' + @app.route("/gunicorn") def hello(): return "<h1 style='color:blue'>Hello There!</h1>" diff --git a/wqflask/utility/elasticsearch_tools.py b/wqflask/utility/elasticsearch_tools.py new file mode 100644 index 00000000..2d3d5add --- /dev/null +++ b/wqflask/utility/elasticsearch_tools.py @@ -0,0 +1,54 @@ +from elasticsearch import Elasticsearch, TransportError +import logging + +from utility.logger import getLogger +logger = getLogger(__name__) + +from utility.tools import ELASTICSEARCH_HOST, ELASTICSEARCH_PORT + +def get_elasticsearch_connection(): + logger.info("get_elasticsearch_connection") + es = None + try: + assert(ELASTICSEARCH_HOST) + assert(ELASTICSEARCH_PORT) + logger.info("ES HOST",ELASTICSEARCH_HOST) + + 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..8c9fed96 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -251,6 +251,26 @@ assert_dir(JS_GUIX_PATH) JS_GN_PATH = get_setting('JS_GN_PATH') # assert_dir(JS_GN_PATH) +GITHUB_CLIENT_ID = get_setting('GITHUB_CLIENT_ID') +GITHUB_CLIENT_SECRET = get_setting('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('GITHUB_API_URL') +ORCID_CLIENT_ID = get_setting('ORCID_CLIENT_ID') +ORCID_CLIENT_SECRET = get_setting('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('ORCID_TOKEN_URL') + +ELASTICSEARCH_HOST = get_setting('ELASTICSEARCH_HOST') +ELASTICSEARCH_PORT = get_setting('ELASTICSEARCH_PORT') + +SMTP_CONNECT = get_setting('SMTP_CONNECT') +SMTP_USERNAME = get_setting('SMTP_USERNAME') +SMTP_PASSWORD = get_setting('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/marker_regression/marker_regression.py b/wqflask/wqflask/marker_regression/marker_regression.py index 087b95b4..c189fb96 100644 --- a/wqflask/wqflask/marker_regression/marker_regression.py +++ b/wqflask/wqflask/marker_regression/marker_regression.py @@ -114,7 +114,7 @@ class MarkerRegression(object): self.num_perm = 0 self.perm_output = [] self.bootstrap_results = [] - self.covariates = start_vars['covariates'] + self.covariates = start_vars['covariates'] if "covariates" in start_vars else None #ZS: This is passed to GN1 code for single chr mapping self.selected_chr = -1 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..c344ea27 100644 --- a/wqflask/wqflask/user_manager.py +++ b/wqflask/wqflask/user_manager.py @@ -54,6 +54,9 @@ logger = getLogger(__name__) from base.data_set import create_datasets_list +import requests +from utility.elasticsearch_tools import get_elasticsearch_connection, get_user_by_unique_column, save_user + THREE_DAYS = 60 * 60 * 24 * 3 #THREE_DAYS = 45 @@ -119,7 +122,8 @@ class AnonUser(object): return collections def import_traits_to_user(self): - collections_list = json.loads(Redis.get(self.key)) + result = Redis.get(self.key) + collections_list = json.loads(result if result else "[]") for collection in collections_list: uc = model.UserCollection() uc.name = collection['name'] @@ -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,61 @@ 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 = get_elasticsearch_connection() + 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 +520,85 @@ 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"]) + es = get_elasticsearch_connection() + 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")) + + es = get_elasticsearch_connection() + 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 +606,56 @@ class LoginUser(object): def __init__(self): self.remember_me = False + def oauth2_login(self, login_type, user_id): + """Login via an OAuth2 provider""" + es = get_elasticsearch_connection() + 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) + es = get_elasticsearch_connection() 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 + } + assert(es is not None) + 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] + 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 +683,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 @@ -558,7 +692,6 @@ class LoginUser(object): """The meat of the logging in process""" session_id_signed = self.successful_login(user, assumed_by) flash("Thank you for logging in {}.".format(user.full_name), "alert-success") - print("IMPORT1:", import_collections) response = make_response(redirect(url_for('index_page', import_collections=import_collections))) if self.remember_me: max_age = self.remember_time @@ -589,8 +722,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 +749,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 +825,18 @@ def register(): params = request.form if request.form else request.args + params = params.to_dict(flat=True) + es = get_elasticsearch_connection() + 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 +907,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() |