From 04b32f95279602da4cfb00b4f356a04eee48af51 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 9 Mar 2020 11:54:34 -0500 Subject: I think this should complete consolidating all the collections code. --- wqflask/base/trait.py | 8 +- wqflask/utility/hmac.py | 56 ++- wqflask/wqflask/collect.py | 247 ++------- wqflask/wqflask/correlation/corr_scatter_plot.py | 9 +- wqflask/wqflask/gsearch.py | 4 +- wqflask/wqflask/search_results.py | 2 +- wqflask/wqflask/templates/base.html | 4 - wqflask/wqflask/templates/collections/list.html | 24 - wqflask/wqflask/templates/collections/view.html | 9 +- .../wqflask/templates/new_security/login_user.html | 1 + .../templates/show_trait_mapping_tools.html | 11 - .../wqflask/templates/show_trait_statistics.html | 6 - wqflask/wqflask/user_login.py | 8 +- wqflask/wqflask/user_session.py | 551 +++++++++++---------- wqflask/wqflask/views.py | 2 +- 15 files changed, 375 insertions(+), 567 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index c9dfe780..8e779a11 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -303,7 +303,7 @@ def jsonable_table_row(trait, dataset_name, index): additive = "N/A" else: additive = "%.3f" % round(float(trait.additive), 2) - return ['', + return ['', index, ''+str(trait.name)+'', trait.symbol, @@ -319,7 +319,7 @@ def jsonable_table_row(trait, dataset_name, index): else: additive = "%.2f" % round(float(trait.additive), 2) if trait.pubmed_id: - return ['', + return ['', index, ''+str(trait.name)+'', trait.description_display, @@ -329,7 +329,7 @@ def jsonable_table_row(trait, dataset_name, index): trait.LRS_location_repr, additive] else: - return ['', + return ['', index, ''+str(trait.name)+'', trait.description_display, @@ -339,7 +339,7 @@ def jsonable_table_row(trait, dataset_name, index): trait.LRS_location_repr, additive] elif dataset.type == "Geno": - return ['', + return ['', index, ''+str(trait.name)+'', trait.location_repr] diff --git a/wqflask/utility/hmac.py b/wqflask/utility/hmac.py index 47001e54..d8a0eace 100644 --- a/wqflask/utility/hmac.py +++ b/wqflask/utility/hmac.py @@ -1,18 +1,38 @@ -from __future__ import print_function, division, absolute_import - -import hmac - -from wqflask import app - -def hmac_creation(stringy): - """Helper function to create the actual hmac""" - - secret = app.config['SECRET_HMAC_CODE'] - - hmaced = hmac.new(secret, stringy, hashlib.sha1) - hm = hmaced.hexdigest() - # ZS: Leaving the below comment here to ask Pjotr about - # "Conventional wisdom is that you don't lose much in terms of security if you throw away up to half of the output." - # http://www.w3.org/QA/2009/07/hmac_truncation_in_xml_signatu.html - hm = hm[:20] - return hm \ No newline at end of file +from __future__ import print_function, division, absolute_import + +import hmac +import hashlib + +from wqflask import app + +def hmac_creation(stringy): + """Helper function to create the actual hmac""" + + secret = app.config['SECRET_HMAC_CODE'] + + hmaced = hmac.new(secret, stringy, hashlib.sha1) + hm = hmaced.hexdigest() + # ZS: Leaving the below comment here to ask Pjotr about + # "Conventional wisdom is that you don't lose much in terms of security if you throw away up to half of the output." + # http://www.w3.org/QA/2009/07/hmac_truncation_in_xml_signatu.html + hm = hm[:20] + return hm + +def data_hmac(stringy): + """Takes arbitray data string and appends :hmac so we know data hasn't been tampered with""" + return stringy + ":" + hmac_creation(stringy) + +def url_for_hmac(endpoint, **values): + """Like url_for but adds an hmac at the end to insure the url hasn't been tampered with""" + + url = url_for(endpoint, **values) + + hm = hmac_creation(url) + if '?' in url: + combiner = "&" + else: + combiner = "?" + return url + combiner + "hm=" + hm + +app.jinja_env.globals.update(url_for_hmac=url_for_hmac, + data_hmac=data_hmac) \ No newline at end of file diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index c273af59..e5c24873 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -8,7 +8,6 @@ import time import uuid import hashlib -import hmac import base64 import urlparse @@ -28,9 +27,8 @@ from pprint import pformat as pf from wqflask.database import db_session from wqflask import model -from wqflask import user_manager -from utility import Bunch, Struct +from utility import Bunch, Struct, hmac from utility.formatting import numify from base import trait @@ -40,119 +38,15 @@ import logging from utility.logger import getLogger logger = getLogger(__name__) - -class AnonCollection(object): - """User is not logged in""" - def __init__(self, collection_name): - anon_user = user_manager.AnonUser() - self.key = anon_user.key - self.name = collection_name - self.id = None - self.created_timestamp = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - self.changed_timestamp = self.created_timestamp #ZS: will be updated when changes are made - - #ZS: Find id and set it if the collection doesn't already exist - if Redis.get(self.key) == "None" or Redis.get(self.key) == None: - Redis.set(self.key, "None") #ZS: For some reason I get the error "Operation against a key holding the wrong kind of value" if I don't do this - else: - collections_list = json.loads(Redis.get(self.key)) - collection_position = 0 #ZS: Position of collection in collection_list, if it exists - collection_exists = False - for i, collection in enumerate(collections_list): - if collection['name'] == self.name: - collection_position = i - collection_exists = True - self.id = collection['id'] - break - - if self.id == None: - self.id = str(uuid.uuid4()) - - def get_members(self): - traits = [] - collections_list = json.loads(Redis.get(self.key)) - for collection in collections_list: - if collection['id'] == self.id: - traits = collection['members'] - return traits - - @property - def num_members(self): - num_members = 0 - collections_list = json.loads(Redis.get(self.key)) - for collection in collections_list: - if collection['id'] == self.id: - num_members = collection['num_members'] - return num_members - - def add_traits(self, unprocessed_traits): - #assert collection_name == "Default", "Unexpected collection name for anonymous user" - self.traits = list(process_traits(unprocessed_traits)) - existing_collections = Redis.get(self.key) - logger.debug("existing_collections:", existing_collections) - if existing_collections != None and existing_collections != "None": - collections_list = json.loads(existing_collections) - collection_position = 0 #ZS: Position of collection in collection_list, if it exists - collection_exists = False - for i, collection in enumerate(collections_list): - if collection['id'] == self.id: - collection_position = i - collection_exists = True - break - if collection_exists: - collections_list[collection_position]['members'] += list(set(self.traits) - set(collections_list[collection_position]['members'])) - collections_list[collection_position]['num_members'] = len(collections_list[collection_position]['members']) - collections_list[collection_position]['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - else: - collection_dict = {"id" : self.id, - "name" : self.name, - "created_timestamp" : self.created_timestamp, - "changed_timestamp" : self.changed_timestamp, - "num_members" : len(self.traits), - "members" : self.traits} - collections_list.append(collection_dict) - else: - collections_list = [] - collection_dict = {"id" : self.id, - "name" : self.name, - "created_timestamp" : self.created_timestamp, - "changed_timestamp" : self.changed_timestamp, - "num_members" : len(self.traits), - "members" : self.traits} - collections_list.append(collection_dict) - - Redis.set(self.key, json.dumps(collections_list)) - - def remove_traits(self, params): - traits_to_remove = [(":").join(trait.split(":")[:2]) for trait in params.getlist('traits[]')] - existing_collections = Redis.get(self.key) - collection_position = 0 - collections_list = json.loads(existing_collections) - for i, collection in enumerate(collections_list): - if collection['id'] == self.id: - collection_position = i - collection_exists = True - break - collections_list[collection_position]['members'] = [trait for trait in collections_list[collection_position]['members'] if trait not in traits_to_remove] - collections_list[collection_position]['num_members'] = len(collections_list[collection_position]['members']) - collections_list[collection_position]['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - len_now = collections_list[collection_position]['num_members'] - Redis.set(self.key, json.dumps(collections_list)) - - # We need to return something so we'll return this...maybe in the future - # we can use it to check the results - return str(len_now) - def process_traits(unprocessed_traits): if isinstance(unprocessed_traits, basestring): unprocessed_traits = unprocessed_traits.split(",") traits = set() for trait in unprocessed_traits: - #print("trait is:", trait) data, _separator, hmac = trait.rpartition(':') data = data.strip() if g.user_session.logged_in: - assert hmac==user_manager.actual_hmac_creation(data), "Data tampering?" + assert hmac == hmac.data_hmac(data), "Data tampering?" traits.add(str(data)) return traits @@ -160,8 +54,6 @@ def process_traits(unprocessed_traits): def report_change(len_before, len_now): new_length = len_now - len_before if new_length: - logger.debug("We've added {} to your collection.".format( - numify(new_length, 'new trait', 'new traits'))) flash("We've added {} to your collection.".format( numify(new_length, 'new trait', 'new traits'))) else: @@ -180,18 +72,14 @@ def store_traits_list(): @app.route("/collections/add") def collections_add(): - if g.user_session.logged_in: + + collections = g.user_session.user_collections + if len(collections) < 1: + collection_name = "Default Collection" + uc_id = g.user_session.add_collection(collection_name, set()) collections = g.user_session.user_collections - if len(collections) < 1: - collection_name = "Default Collection" - uc_id = g.user_session.add_collection(collection_name, set()) - collections = g.user_session.user_collections - else: - anon_collections = user_manager.AnonUser().get_collections() - collections = [] - for collection in anon_collections: - collections.append({'id':collection['id'], 'name':collection['name']}) + #ZS: One of these might be unnecessary if 'traits' in request.args: traits=request.args['traits'] return render_template("collections/add.html", @@ -212,19 +100,13 @@ def collections_new(): if "sign_in" in params: return redirect(url_for('login')) if "create_new" in params: - logger.debug("in create_new") collection_name = params['new_collection'] if collection_name.strip() == "": collection_name = datetime.datetime.utcnow().strftime('Collection_%b_%d_%H:%M') return create_new(collection_name) elif "add_to_existing" in params: - logger.debug("in add to existing") if 'existing_collection' not in params: - default_collection_exists = False - if g.user_session.logged_in: - collections = g.user_session.user_collections - else: - collections = user_manager.AnonUser().get_collections() + collections = g.user_session.user_collections for collection in collections: if collection["name"] == "Default Collection": collection_id = collection["id"] @@ -235,22 +117,14 @@ def collections_new(): else: collection_id = params['existing_collection'].split(":")[0] collection_name = params['existing_collection'].split(":")[1] - if g.user_session.logged_in: - if "hash" in params: - unprocessed_traits = Redis.get(params['hash']) - else: - unprocessed_traits = params['traits'] - traits = list(process_traits(unprocessed_traits)) - g.user_session.add_traits_to_collection(collection_id, traits) - return redirect(url_for('view_collection', uc_id=collection_id)) + + if "hash" in params: + unprocessed_traits = Redis.get(params['hash']) else: - ac = AnonCollection(collection_name) - if "hash" in params: - unprocessed_traits = Redis.get(params['hash']) - else: - unprocessed_traits = params['traits'] - ac.add_traits(unprocessed_traits) - return redirect(url_for('view_collection', collection_id=ac.id)) + unprocessed_traits = params['traits'] + traits = list(process_traits(unprocessed_traits)) + g.user_session.add_traits_to_collection(collection_id, traits) + return redirect(url_for('view_collection', uc_id=collection_id)) else: CauseAnError @@ -265,50 +139,30 @@ def create_new(collection_name): traits = process_traits(unprocessed_traits) - if g.user_session.logged_in: - uc_id = g.user_session.add_collection(collection_name, traits) + uc_id = g.user_session.add_collection(collection_name, traits) - return redirect(url_for('view_collection', uc_id=uc_id)) - else: - ac = AnonCollection(collection_name) - ac.changed_timestamp = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - ac.add_traits(unprocessed_traits) - return redirect(url_for('view_collection', collection_id=ac.id)) + return redirect(url_for('view_collection', uc_id=uc_id)) @app.route("/collections/list") def list_collections(): params = request.args - if g.user_session.logged_in: - user_collections = list(g.user_session.user_collections) - #logger.debug("user_collections are:", user_collections) - return render_template("collections/list.html", - params = params, - collections = user_collections, - ) - else: - anon_collections = user_manager.AnonUser().get_collections() - #logger.debug("anon_collections are:", anon_collections) - return render_template("collections/list.html", - params = params, - collections = anon_collections) - + user_collections = list(g.user_session.user_collections) + return render_template("collections/list.html", + params = params, + collections = user_collections, + ) @app.route("/collections/remove", methods=('POST',)) def remove_traits(): params = request.form - if "uc_id" in params: - uc_id = params['uc_id'] - traits_to_remove = params.getlist('traits[]') - traits_to_remove = process_traits(traits_to_remove) - logger.debug("\n\n after processing, traits_to_remove:", traits_to_remove) - - members_now = g.user_session.remove_traits_from_collection(uc_id, traits_to_remove) - else: - collection_name = params['collection_name'] - members_now = AnonCollection(collection_name).remove_traits(params) + uc_id = params['uc_id'] + traits_to_remove = params.getlist('traits[]') + traits_to_remove = process_traits(traits_to_remove) + logger.debug("\n\n after processing, traits_to_remove:", traits_to_remove) + members_now = g.user_session.remove_traits_from_collection(uc_id, traits_to_remove) # We need to return something so we'll return this...maybe in the future # we can use it to check the results @@ -319,21 +173,13 @@ def remove_traits(): def delete_collection(): params = request.form uc_id = "" - if g.user_session.logged_in: - uc_id = params['uc_id'] - if len(uc_id.split(":")) > 1: - for this_uc_id in uc_id.split(":"): - collection_name = g.user_session.delete_collection(this_uc_id) - else: - collection_name = g.user_session.delete_collection(uc_id) + + uc_id = params['uc_id'] + if len(uc_id.split(":")) > 1: + for this_uc_id in uc_id.split(":"): + collection_name = g.user_session.delete_collection(this_uc_id) else: - if "collection_name" in params: - collection_name = params['collection_name'] - user_manager.AnonUser().delete_collection(collection_name) - else: - uc_id = params['uc_id'] - for this_collection in uc_id.split(":"): - user_manager.AnonUser().delete_collection(this_collection) + collection_name = g.user_session.delete_collection(uc_id) if uc_id != "": if len(uc_id.split(":")) > 1: @@ -350,19 +196,9 @@ def delete_collection(): def view_collection(): params = request.args - if g.user_session.logged_in and "uc_id" in params: - uc_id = params['uc_id'] - uc = (collection for collection in g.user_session.user_collections if collection["id"] == uc_id).next() - traits = uc["members"] - else: - user_collections = json.loads(Redis.get(user_manager.AnonUser().key)) - this_collection = {} - for collection in user_collections: - if collection['id'] == params['collection_id']: - this_collection = collection - break - - traits = this_collection['members'] + uc_id = params['uc_id'] + uc = (collection for collection in g.user_session.user_collections if collection["id"] == uc_id).next() + traits = uc["members"] trait_obs = [] json_version = [] @@ -381,12 +217,9 @@ def view_collection(): json_version.append(trait.jsonable(trait_ob)) - if "uc_id" in params: - collection_info = dict(trait_obs=trait_obs, - uc = uc) - else: - collection_info = dict(trait_obs=trait_obs, - collection_name=this_collection['name']) + collection_info = dict(trait_obs=trait_obs, + uc = uc) + if "json" in params: return json.dumps(json_version) else: diff --git a/wqflask/wqflask/correlation/corr_scatter_plot.py b/wqflask/wqflask/correlation/corr_scatter_plot.py index f8eca386..dfb81c54 100644 --- a/wqflask/wqflask/correlation/corr_scatter_plot.py +++ b/wqflask/wqflask/correlation/corr_scatter_plot.py @@ -71,15 +71,8 @@ class CorrScatterPlot(object): sr_intercept_coords = get_intercept_coords(srslope, srintercept, sr_range, sr_range) - #vals_3 = [] - #for sample in self.trait_3.data: - # vals_3.append(self.trait_3.data[sample].value) - self.collections_exist = "False" - if g.user_session.logged_in: - if g.user_session.num_collections > 0: - self.collections_exist = "True" - elif g.cookie_session.display_num_collections() != "": + if g.user_session.num_collections > 0: self.collections_exist = "True" self.js_data = dict( diff --git a/wqflask/wqflask/gsearch.py b/wqflask/wqflask/gsearch.py index ed3b74b8..12813e9a 100644 --- a/wqflask/wqflask/gsearch.py +++ b/wqflask/wqflask/gsearch.py @@ -70,7 +70,7 @@ class GSearch(object): this_trait['name'] = line[5] this_trait['dataset'] = line[3] this_trait['dataset_fullname'] = line[4] - this_trait['hmac'] = hmac.hmac_creation('{}:{}'.format(line[5], line[3])) + this_trait['hmac'] = hmac.data_hmac('{}:{}'.format(line[5], line[3])) this_trait['species'] = line[0] this_trait['group'] = line[1] this_trait['tissue'] = line[2] @@ -174,7 +174,7 @@ class GSearch(object): this_trait['display_name'] = this_trait['name'] this_trait['dataset'] = line[2] this_trait['dataset_fullname'] = line[3] - this_trait['hmac'] = hmac.hmac_creation('{}:{}'.format(line[4], line[2])) + this_trait['hmac'] = hmac.data_hmac('{}:{}'.format(line[4], line[2])) this_trait['species'] = line[0] this_trait['group'] = line[1] if line[9] != None and line[6] != None: diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 902317a4..34c647f4 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -120,7 +120,7 @@ views.py). else: trait_dict['display_name'] = this_trait.name trait_dict['dataset'] = this_trait.dataset.name - trait_dict['hmac'] = hmac.hmac_creation('{}:{}'.format(this_trait.name, this_trait.dataset.name)) + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) if this_trait.dataset.type == "ProbeSet": trait_dict['symbol'] = this_trait.symbol trait_dict['description'] = this_trait.description_display.decode('utf-8', 'replace') diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 04cf718a..2c95bf5e 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -73,11 +73,7 @@
  • Collections - {% if g.user_session.logged_in %} {{ g.user_session.num_collections }} - {% else %} - {{ g.cookie_session.display_num_collections() }} - {% endif %}
  • diff --git a/wqflask/wqflask/templates/collections/list.html b/wqflask/wqflask/templates/collections/list.html index 19aec8ae..eb3fc061 100644 --- a/wqflask/wqflask/templates/collections/list.html +++ b/wqflask/wqflask/templates/collections/list.html @@ -15,17 +15,6 @@

    Your Collections

    {% endif %} - - -
    @@ -55,24 +44,11 @@ {% for uc in collections %} - {% if g.user_session.logged_in %} - {% else %} - - {% endif %} {{ loop.index }} - {% if g.user_session.logged_in %} {{ uc.name }} - {% else %} - {{ uc.name }} - {% endif %} - {% if g.user_session.logged_in %} {{ uc.created_timestamp }} {{ uc.changed_timestamp }} - {% else %} - {{ timeago(uc.created_timestamp.isoformat() + "Z") }} - {{ timeago(uc.changed_timestamp.isoformat() + "Z") }} - {% endif %} {{ uc.num_members }} {% endfor %} diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 640eb561..fd0025d9 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -9,20 +9,15 @@
    - {% if uc %} -

    {{ uc.name }}

    - {% else %} -

    {{ collection_name }}

    - {% endif %} +

    +

    {{ uc.name }}

    This collection has {{ '{}'.format(numify(trait_obs|count, "record", "records")) }}

    - {% if uc %} - {% endif %} diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html index 9df0e16a..80fed82a 100644 --- a/wqflask/wqflask/templates/new_security/login_user.html +++ b/wqflask/wqflask/templates/new_security/login_user.html @@ -10,6 +10,7 @@ {% if redis_is_available: %} +
    diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index 841d6ad5..a806a8b3 100644 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -81,7 +81,6 @@ Select covariate(s) from a collection
    - {% if g.user_session.logged_in %} {% if g.user_session.num_collections < 1 %} No collections available. Please add traits to a collection to use them as covariates. {% else %} @@ -91,16 +90,6 @@
    {% endif %} - {% elif g.cookie_session.display_num_collections() == "" %} - No collections available. Please add traits to a collection to use them as covariates. - {% else %} -
    - - -
    -
    - - {% endif %}
    diff --git a/wqflask/wqflask/templates/show_trait_statistics.html b/wqflask/wqflask/templates/show_trait_statistics.html index 4653b398..974081d3 100644 --- a/wqflask/wqflask/templates/show_trait_statistics.html +++ b/wqflask/wqflask/templates/show_trait_statistics.html @@ -21,11 +21,9 @@
  • Violin Plot
  • - {% if g.user_session.logged_in %}
  • Scatterplot Matrix
  • - {% endif %}
    @@ -78,13 +76,11 @@ Sort By Value
    - {% if g.user_session.logged_in %}
    - {% endif %}
    @@ -126,7 +122,6 @@
    - {% if g.user_session.logged_in %}
    @@ -141,7 +136,6 @@
    - {% endif %} diff --git a/wqflask/wqflask/user_login.py b/wqflask/wqflask/user_login.py index 05885e2c..da3cc504 100644 --- a/wqflask/wqflask/user_login.py +++ b/wqflask/wqflask/user_login.py @@ -21,9 +21,9 @@ from flask import (Flask, g, render_template, url_for, request, make_response, from wqflask import app from wqflask import pbkdf2 -from wqflask.hmac_func import hmac_creation from wqflask.user_session import UserSession +from utility import hmac from utility.redis_tools import is_redis_available, get_user_id, get_user_by_unique_column, set_user_attribute, save_user, save_verification_code, check_verification_code, get_user_collections, save_collections from utility.logger import getLogger @@ -83,7 +83,7 @@ def encrypt_password(unencrypted_password, pwfields): def get_signed_session_id(user): session_id = str(uuid.uuid4()) - session_id_signature = hmac_creation(session_id) + session_id_signature = hmac.hmac_creation(session_id) session_id_signed = session_id + ":" + session_id_signature #ZS: Need to check if this is ever actually used or exists @@ -197,12 +197,14 @@ def login(): if password_match: # If password correct if user_details['confirmed']: # If account confirmed import_col = "false" + anon_id = "" if 'import_collections' in params: import_col = "true" + anon_id = params['anon_id'] session_id_signed = get_signed_session_id(user_details) flash("Thank you for logging in {}.".format(user_details['full_name']), "alert-success") - response = make_response(redirect(url_for('index_page', import_collections = import_col))) + response = make_response(redirect(url_for('index_page', import_collections = import_col, anon_id = anon_id))) response.set_cookie(UserSession.user_cookie_name, session_id_signed, max_age=None) return response else: diff --git a/wqflask/wqflask/user_session.py b/wqflask/wqflask/user_session.py index 1f3e6558..e19ec9df 100644 --- a/wqflask/wqflask/user_session.py +++ b/wqflask/wqflask/user_session.py @@ -1,272 +1,281 @@ -from __future__ import print_function, division, absolute_import - -import datetime -import time -import uuid - -import simplejson as json - -import redis # used for collections -Redis = redis.StrictRedis() - -from flask import (Flask, g, render_template, url_for, request, make_response, - redirect, flash, abort) - -from wqflask import app -from wqflask.hmac_func import hmac_creation - -#from utility.elasticsearch_tools import get_elasticsearch_connection -from utility.redis_tools import get_user_id, get_user_by_unique_column, get_user_collections, save_collections - -from utility.logger import getLogger -logger = getLogger(__name__) - -THREE_DAYS = 60 * 60 * 24 * 3 -THIRTY_DAYS = 60 * 60 * 24 * 30 - -def verify_cookie(cookie): - the_uuid, separator, the_signature = cookie.partition(':') - assert len(the_uuid) == 36, "Is session_id a uuid?" - assert separator == ":", "Expected a : here" - assert the_signature == hmac_creation(the_uuid), "Uh-oh, someone tampering with the cookie?" - return the_uuid - -def create_signed_cookie(): - the_uuid = str(uuid.uuid4()) - signature = hmac_creation(the_uuid) - uuid_signed = the_uuid + ":" + signature - logger.debug("uuid_signed:", uuid_signed) - return the_uuid, uuid_signed - -class UserSession(object): - """Logged in user handling""" - - user_cookie_name = 'session_id_v1' - anon_cookie_name = 'anon_user_v1' - - def __init__(self): - user_cookie = request.cookies.get(self.user_cookie_name) - if not user_cookie: - self.logged_in = False - anon_cookie = request.cookies.get(self.anon_cookie_name) - self.cookie_name = self.anon_cookie_name - if anon_cookie: - self.cookie = anon_cookie - session_id = verify_cookie(self.cookie) - else: - session_id, self.cookie = create_signed_cookie() - else: - self.cookie_name = self.user_cookie_name - self.cookie = user_cookie - session_id = verify_cookie(self.cookie) - - self.redis_key = self.cookie_name + ":" + session_id - self.session_id = session_id - self.record = Redis.hgetall(self.redis_key) - - #ZS: If user correctled logged in but their session expired - #ZS: Need to test this by setting the time-out to be really short or something - if not self.record: - if user_cookie: - self.logged_in = False - - ########### Grrr...this won't work because of the way flask handles cookies - # Delete the cookie - response = make_response(redirect(url_for('login'))) - #response.set_cookie(self.cookie_name, '', expires=0) - flash("Due to inactivity your session has expired. If you'd like please login again.") - return response - #return - else: - self.record = dict(login_time = time.time(), - user_type = "anon", - user_id = str(uuid.uuid4())) - - Redis.hmset(self.redis_key, self.record) - Redis.expire(self.redis_key, THIRTY_DAYS) - else: - if user_cookie: - self.logged_in = True - - if user_cookie: - session_time = THREE_DAYS - else: - session_time = THIRTY_DAYS - - if Redis.ttl(self.redis_key) < session_time: - # (Almost) everytime the user does something we extend the session_id in Redis... - logger.debug("Extending ttl...") - Redis.expire(self.redis_key, session_time) - - @property - def user_id(self): - """Shortcut to the user_id""" - if 'user_id' in self.record: - return self.record['user_id'] - else: - return '' - - @property - def redis_user_id(self): - """User id from Redis (need to check if this is the same as the id stored in self.records)""" - - #ZS: This part is a bit weird. Some accounts used to not have saved user ids, and in the process of testing I think I created some duplicate accounts for myself. - #ZS: Accounts should automatically generate user_ids if they don't already have one now, so this might not be necessary for anything other than my account's collections - - if 'user_email_address' in self.record: - user_email = self.record['user_email_address'] - - #ZS: Get user's collections if they exist - user_id = None - user_id = get_user_id("email_address", user_email) - elif 'user_id' in self.record: - user_id = self.record['user_id'] - elif 'github_id' in self.record: - user_github_id = self.record['github_id'] - user_id = None - user_id = get_user_id("github_id", user_github_id) - else: #ZS: Anonymous user - return None - - return user_id - - @property - def user_name(self): - """Shortcut to the user_name""" - if 'user_name' in self.record: - return self.record['user_name'] - else: - return '' - - @property - def user_collections(self): - """List of user's collections""" - - #ZS: Get user's collections if they exist - collections = get_user_collections(self.redis_user_id) - return collections - - @property - def num_collections(self): - """Number of user's collections""" - - return len(self.user_collections) - - def add_collection(self, collection_name, traits): - """Add collection into ElasticSearch""" - - collection_dict = {'id': unicode(uuid.uuid4()), - 'name': collection_name, - 'created_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), - 'changed_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), - 'num_members': len(traits), - 'members': list(traits) } - - current_collections = self.user_collections - current_collections.append(collection_dict) - self.update_collections(current_collections) - - return collection_dict['id'] - - def change_collection_name(self, collection_id, new_name): - for collection in self.user_collections: - if collection['id'] == collection_id: - collection['name'] = new_name - break - - return new_name - - def delete_collection(self, collection_id): - """Remove collection with given ID""" - - updated_collections = [] - for collection in self.user_collections: - if collection['id'] == collection_id: - continue - else: - updated_collections.append(collection) - - self.update_collections(updated_collections) - - return collection['name'] - - def add_traits_to_collection(self, collection_id, traits_to_add): - """Add specified traits to a collection""" - - this_collection = self.get_collection_by_id(collection_id) - - updated_collection = this_collection - updated_traits = this_collection['members'] + traits_to_add - - updated_collection['members'] = updated_traits - updated_collection['num_members'] = len(updated_traits) - updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - - updated_collections = [] - for collection in self.user_collections: - if collection['id'] == collection_id: - updated_collections.append(updated_collection) - else: - updated_collections.append(collection) - - self.update_collections(updated_collections) - - def remove_traits_from_collection(self, collection_id, traits_to_remove): - """Remove specified traits from a collection""" - - this_collection = self.get_collection_by_id(collection_id) - - updated_collection = this_collection - updated_traits = [] - for trait in this_collection['members']: - if trait in traits_to_remove: - continue - else: - updated_traits.append(trait) - - updated_collection['members'] = updated_traits - updated_collection['num_members'] = len(updated_traits) - updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - - updated_collections = [] - for collection in self.user_collections: - if collection['id'] == collection_id: - updated_collections.append(updated_collection) - else: - updated_collections.append(collection) - - self.update_collections(updated_collections) - - return updated_traits - - def get_collection_by_id(self, collection_id): - for collection in self.user_collections: - if collection['id'] == collection_id: - return collection - - def get_collection_by_name(self, collection_name): - for collection in self.user_collections: - if collection['name'] == collection_name: - return collection - - return None - - def update_collections(self, updated_collections): - collection_body = json.dumps(updated_collections) - - save_collections(self.redis_user_id, collection_body) - - def delete_session(self): - # And more importantly delete the redis record - Redis.delete(self.redis_key) - self.logged_in = False - -@app.before_request -def before_request(): - g.user_session = UserSession() - -@app.after_request -def set_cookie(response): - if not request.cookies.get(g.user_session.cookie_name): - response.set_cookie(g.user_session.cookie_name, g.user_session.cookie) +from __future__ import print_function, division, absolute_import + +import datetime +import time +import uuid + +import simplejson as json + +import redis # used for collections +Redis = redis.StrictRedis() + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash, abort) + +from wqflask import app +from utility import hmac + +#from utility.elasticsearch_tools import get_elasticsearch_connection +from utility.redis_tools import get_user_id, get_user_by_unique_column, get_user_collections, save_collections + +from utility.logger import getLogger +logger = getLogger(__name__) + +THREE_DAYS = 60 * 60 * 24 * 3 +THIRTY_DAYS = 60 * 60 * 24 * 30 + +def verify_cookie(cookie): + the_uuid, separator, the_signature = cookie.partition(':') + assert len(the_uuid) == 36, "Is session_id a uuid?" + assert separator == ":", "Expected a : here" + assert the_signature == hmac.hmac_creation(the_uuid), "Uh-oh, someone tampering with the cookie?" + return the_uuid + +def create_signed_cookie(): + the_uuid = str(uuid.uuid4()) + signature = hmac.hmac_creation(the_uuid) + uuid_signed = the_uuid + ":" + signature + logger.debug("uuid_signed:", uuid_signed) + return the_uuid, uuid_signed + +class UserSession(object): + """Logged in user handling""" + + user_cookie_name = 'session_id_v1' + anon_cookie_name = 'anon_user_v1' + + def __init__(self): + user_cookie = request.cookies.get(self.user_cookie_name) + if not user_cookie: + self.logged_in = False + anon_cookie = request.cookies.get(self.anon_cookie_name) + self.cookie_name = self.anon_cookie_name + if anon_cookie: + self.cookie = anon_cookie + session_id = verify_cookie(self.cookie) + else: + session_id, self.cookie = create_signed_cookie() + else: + self.cookie_name = self.user_cookie_name + self.cookie = user_cookie + session_id = verify_cookie(self.cookie) + + self.redis_key = self.cookie_name + ":" + session_id + self.session_id = session_id + self.record = Redis.hgetall(self.redis_key) + + #ZS: If user correctled logged in but their session expired + #ZS: Need to test this by setting the time-out to be really short or something + if not self.record: + if user_cookie: + self.logged_in = False + + ########### Grrr...this won't work because of the way flask handles cookies + # Delete the cookie + response = make_response(redirect(url_for('login'))) + #response.set_cookie(self.cookie_name, '', expires=0) + flash("Due to inactivity your session has expired. If you'd like please login again.") + return response + #return + else: + self.record = dict(login_time = time.time(), + user_type = "anon", + user_id = str(uuid.uuid4())) + + Redis.hmset(self.redis_key, self.record) + Redis.expire(self.redis_key, THIRTY_DAYS) + else: + if user_cookie: + self.logged_in = True + + if user_cookie: + session_time = THREE_DAYS + else: + session_time = THIRTY_DAYS + + if Redis.ttl(self.redis_key) < session_time: + # (Almost) everytime the user does something we extend the session_id in Redis... + logger.debug("Extending ttl...") + Redis.expire(self.redis_key, session_time) + + @property + def user_id(self): + """Shortcut to the user_id""" + if 'user_id' in self.record: + return self.record['user_id'] + else: + return '' + + @property + def redis_user_id(self): + """User id from Redis (need to check if this is the same as the id stored in self.records)""" + + #ZS: This part is a bit weird. Some accounts used to not have saved user ids, and in the process of testing I think I created some duplicate accounts for myself. + #ZS: Accounts should automatically generate user_ids if they don't already have one now, so this might not be necessary for anything other than my account's collections + + if 'user_email_address' in self.record: + user_email = self.record['user_email_address'] + + #ZS: Get user's collections if they exist + user_id = None + user_id = get_user_id("email_address", user_email) + elif 'user_id' in self.record: + user_id = self.record['user_id'] + elif 'github_id' in self.record: + user_github_id = self.record['github_id'] + user_id = None + user_id = get_user_id("github_id", user_github_id) + else: #ZS: Anonymous user + return None + + return user_id + + @property + def user_name(self): + """Shortcut to the user_name""" + if 'user_name' in self.record: + return self.record['user_name'] + else: + return '' + + @property + def user_collections(self): + """List of user's collections""" + + #ZS: Get user's collections if they exist + collections = get_user_collections(self.redis_user_id) + return collections + + @property + def num_collections(self): + """Number of user's collections""" + + return len(self.user_collections) + + def add_collection(self, collection_name, traits): + """Add collection into Redis""" + + collection_dict = {'id': unicode(uuid.uuid4()), + 'name': collection_name, + 'created_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), + 'changed_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), + 'num_members': len(traits), + 'members': list(traits) } + + current_collections = self.user_collections + current_collections.append(collection_dict) + self.update_collections(current_collections) + + return collection_dict['id'] + + def change_collection_name(self, collection_id, new_name): + for collection in self.user_collections: + if collection['id'] == collection_id: + collection['name'] = new_name + break + + return new_name + + def delete_collection(self, collection_id): + """Remove collection with given ID""" + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + continue + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + return collection['name'] + + def add_traits_to_collection(self, collection_id, traits_to_add): + """Add specified traits to a collection""" + + this_collection = self.get_collection_by_id(collection_id) + + updated_collection = this_collection + updated_traits = this_collection['members'] + traits_to_add + + updated_collection['members'] = updated_traits + updated_collection['num_members'] = len(updated_traits) + updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + updated_collections.append(updated_collection) + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + def remove_traits_from_collection(self, collection_id, traits_to_remove): + """Remove specified traits from a collection""" + + this_collection = self.get_collection_by_id(collection_id) + + updated_collection = this_collection + updated_traits = [] + for trait in this_collection['members']: + if trait in traits_to_remove: + continue + else: + updated_traits.append(trait) + + updated_collection['members'] = updated_traits + updated_collection['num_members'] = len(updated_traits) + updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + updated_collections.append(updated_collection) + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + return updated_traits + + def get_collection_by_id(self, collection_id): + for collection in self.user_collections: + if collection['id'] == collection_id: + return collection + + def get_collection_by_name(self, collection_name): + for collection in self.user_collections: + if collection['name'] == collection_name: + return collection + + return None + + def update_collections(self, updated_collections): + collection_body = json.dumps(updated_collections) + + save_collections(self.redis_user_id, collection_body) + + def import_traits_to_user(self, anon_id): + collections = get_user_collections(anon_id) + for collection in collections: + collection_exists = self.get_collection_by_name(collection['name']) + if collection_exists: + continue + else: + self.add_collection(collection['name'], collection['members']) + + def delete_session(self): + # And more importantly delete the redis record + Redis.delete(self.redis_key) + self.logged_in = False + +@app.before_request +def before_request(): + g.user_session = UserSession() + +@app.after_request +def set_cookie(response): + if not request.cookies.get(g.user_session.cookie_name): + response.set_cookie(g.user_session.cookie_name, g.user_session.cookie) return response \ No newline at end of file diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 436ebc91..8a80c26c 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -126,7 +126,7 @@ def index_page(): if 'import_collections' in params: import_collections = params['import_collections'] if import_collections == "true": - g.cookie_session.import_traits_to_user() + g.user_session.import_traits_to_user(params['anon_id']) #if USE_GN_SERVER: # # The menu is generated using GN_SERVER # return render_template("index_page.html", gn_server_url = GN_SERVER_URL, version=GN_VERSION) -- cgit v1.2.3