diff options
Diffstat (limited to 'wqflask')
-rw-r--r-- | wqflask/utility/elasticsearch_tools.py | 35 | ||||
-rw-r--r-- | wqflask/utility/tools.py | 23 | ||||
-rw-r--r-- | wqflask/wqflask/templates/new_security/login_user.html | 41 | ||||
-rw-r--r-- | wqflask/wqflask/user_manager.py | 170 |
4 files changed, 224 insertions, 45 deletions
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): |