aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/genenetwork27
-rw-r--r--wqflask/utility/elasticsearch_tools.py35
-rw-r--r--wqflask/utility/tools.py23
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html41
-rw-r--r--wqflask/wqflask/user_manager.py170
5 files changed, 230 insertions, 46 deletions
diff --git a/bin/genenetwork2 b/bin/genenetwork2
index f64576d5..34fbc72e 100755
--- a/bin/genenetwork2
+++ b/bin/genenetwork2
@@ -94,6 +94,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)))
@@ -107,7 +112,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
diff --git a/wqflask/utility/elasticsearch_tools.py b/wqflask/utility/elasticsearch_tools.py
new file mode 100644
index 00000000..8b8ad9cc
--- /dev/null
+++ b/wqflask/utility/elasticsearch_tools.py
@@ -0,0 +1,35 @@
+es = None
+try:
+ from elasticsearch import Elasticsearch, TransportError
+ from utility.tools import ELASTICSEARCH_HOST, ELASTICSEARCH_PORT
+
+ es = Elasticsearch([{
+ "host": ELASTICSEARCH_HOST
+ , "port": ELASTICSEARCH_PORT
+ }]) if (ELASTICSEARCH_HOST and ELASTICSEARCH_PORT) else None
+except:
+ es = None
+
+def get_user_by_unique_column(column_name, column_value):
+ user_details = None
+ try:
+ response = es.search(
+ index = "users"
+ , doc_type = "local"
+ , body = {
+ "query": { "match": { column_name: column_value } }
+ })
+ if len(response["hits"]["hits"]) > 0:
+ user_details = response["hits"]["hits"][0]["_source"]
+ except TransportError as te:
+ pass
+ return user_details
+
+def save_user(user, user_id, index="users", doc_type="local"):
+ from time import sleep
+ es = Elasticsearch([{
+ "host": ELASTICSEARCH_HOST
+ , "port": ELASTICSEARCH_PORT
+ }])
+ es.create(index, doc_type, body=user, id=user_id)
+ sleep(1) # Delay 1 second to allow indexing
diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py
index 57f97a81..feeeccfc 100644
--- a/wqflask/utility/tools.py
+++ b/wqflask/utility/tools.py
@@ -251,6 +251,29 @@ 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')
+
PYLMM_COMMAND = app_set("PYLMM_COMMAND",pylmm_command())
GEMMA_COMMAND = app_set("GEMMA_COMMAND",gemma_command())
PLINK_COMMAND = app_set("PLINK_COMMAND",plink_command())
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..772d6c83 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_user_by_unique_column, save_user
+
THREE_DAYS = 60 * 60 * 24 * 3
#THREE_DAYS = 45
@@ -269,15 +272,19 @@ class RegisterUser(object):
self.errors = []
self.user = Bunch()
- 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.')
- self.user.full_name = kw.get('full_name', '').strip()
+ email_exists = get_user_by_unique_column("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', '').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 +301,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(self.user.__dict__, self.user.user_id)
def set_password(password, user):
pwfields = Bunch()
@@ -361,7 +351,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())
)
@@ -492,8 +482,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("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(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("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(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 +566,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("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
+ from utility.elasticsearch_tools import es
+ 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)
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("email_address", params["email_address"])
+ user = 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)
@@ -589,8 +681,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):