import datetime import time import uuid import simplejson as json from flask import (Flask, g, render_template, url_for, request, make_response, redirect, flash, abort) from gn2.wqflask import app from gn2.utility import hmac from gn2.utility.redis_tools import get_redis_conn, get_user_id, get_user_by_unique_column, set_user_attribute, get_user_collections, save_collections Redis = get_redis_conn() THREE_DAYS = 60 * 60 * 24 * 3 THIRTY_DAYS = 60 * 60 * 24 * 30 @app.before_request def get_user_session(): g.user_session = UserSession() # I think this should solve the issue of deleting the cookie and redirecting to the home page when a user's session has expired if not g.user_session: response = make_response(redirect(url_for('login'))) response.set_cookie('session_id_v2', '', expires=0) return response @app.after_request def set_user_session(response): if hasattr(g, 'user_session'): if not request.cookies.get(g.user_session.cookie_name): response.set_cookie(g.user_session.cookie_name, g.user_session.cookie) else: response.set_cookie('session_id_v2', '', expires=0) return response 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 return the_uuid, uuid_signed @app.route("/user/manage", methods=('GET', 'POST')) def manage_user(): params = request.form if request.form else request.args if 'new_full_name' in params: set_user_attribute(g.user_session.user_id, 'full_name', params['new_full_name']) if 'new_organization' in params: set_user_attribute(g.user_session.user_id, 'organization', params['new_organization']) user_details = get_user_by_unique_column("user_id", g.user_session.user_id) return render_template("admin/manage_user.html", user_details=user_details) class UserSession: """Logged in user handling""" user_cookie_name = 'session_id_v2' 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 correctly 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 or self.record == []: if user_cookie: self.logged_in = False 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) # Grrr...this won't work because of the way flask handles cookies # Delete the cookie flash( "Due to inactivity your session has expired. If you'd like please login again.") return None 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 self.user_details = get_user_by_unique_column("user_id", self.user_id) if not self.user_details: self.logged_in = False return None 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... Redis.expire(self.redis_key, session_time) @property def user_id(self): """Shortcut to the user_id""" if b'user_id' not in self.record: self.record[b'user_id'] = str(uuid.uuid4()) try: return self.record[b'user_id'].decode("utf-8") except: return self.record[b'user_id'] @property def user_email(self): """Shortcut to the user email address""" if self.logged_in and 'email_address' in self.user_details: return self.user_details['email_address'] else: return None @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)""" # 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. # 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'] # 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: # 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""" # Get user's collections if they exist collections = get_user_collections(self.user_id) collections = [item for item in collections if item['name'] != "Your Default Collection"] + \ [item for item in collections if item['name'] == "Your Default Collection"] # Ensure Default Collection is last in list return collections @property def num_collections(self): """Number of user's collections""" return len([item for item in self.user_collections if item['num_members'] > 0]) def add_collection(self, collection_name, traits): """Add collection into Redis""" collection_dict = {'id': str(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): updated_collections = [] for collection in self.user_collections: updated_collection = collection if collection['id'] == collection_id: updated_collection['name'] = new_name updated_collections.append(collection) self.update_collections(updated_collections) 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 current_members_minus_new = [ member for member in this_collection['members'] if member not in traits_to_add] updated_traits = traits_to_add + current_members_minus_new 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.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