# The primary purpose of this module is to run data hashing and comparison functions
# It is also called during the intialization of modules to register their hashing and comparison functions.
# Try to leave all the boilerplate calls like DB access and JSON decoding in the main.py file

import hijackingprevention.session as session
import hijackingprevention.api_user as api_user

from tornado import gen

import logging
logger = logging.getLogger(__name__)

OK = (200, 'OK')

class Receiver():
	"""This class handles all requests to hash and interpret data.
	Most of the shared functionalty that underpins the API is defined here.
	Functions in here fall into two catagories:
		Functions that allow modules to register hashers or comparers.
		Functions that run the registered hashers or comparers.
	Please note:
		If a function you are considering including in this file does not invoke a hasher, translator, or comparer it may not belong in this file.
	"""
	def __init__(self):
		self.__hashers = {}
		self.__translators = {}
		self.__comparers = {}
		self.__maxscores = {}
		self.__callbacks = {}

	# Module registration functions

	def add_hasher(self, name, fxn):
		"""This is called by each module on startup to register its sitewide hasher."""
		self.__hashers[name] = fxn

	def add_translator(self, name, fxn):
		"""This is called by each module on startup to register its data translator.
		Translators hash data, which has already been hashed by a (sitewide) hasher, with a per user salt.
		"""
		self.__translators[name] = fxn

	def add_comparer(self, name, fxn, score):
		"""This is called by each module on startup to register its hash comparer."""
		self.__comparers[name] = fxn
		self.__maxscores[name] = score

	# Module users and API interface

	@gen.coroutine
	def add_data(self, req, headers, db):
		"""This function is called when data is received from a browser to hash and store the data"""
		# This function has, rather irritatingly, remained in spite of my attempt to keep database access out of the api.py file.  
		if req['name'] in self.__hashers.keys():
			#setup to invoke hasher
			site = api_user.Site(db)
			yield site.get_by_client_key(req['ck'],
				headers.get("Host"))
			salt = site.get_salt(req['name'])
			#invoke hasher
			hash = yield self.__hashers[req['name']](req['data'],
				headers, salt)
			#store the result
			site_id = site.get_id()
			ses = session.Session(req['sid'], site_id, db) #setup session object
			yield ses.read_db() #read session if it exists
			ses.add_data({req['name']:hash}) #add data to session object
			yield ses.write_out() #update session object in database
			return(OK) #Nothing failed. Right? oops.
		else:
			logger.warning('Could not find a handler for "' + req['name'] + '"')
			return(400, 'Err: Could not find a handler for "' + req['name'] + '"')

	@gen.coroutine
	def copy_data(self, ses, member):
		"""This function is called to store session data permenantly to the user profile"""
		data = {}
		for dt in ses.keys():
			data[dt] = yield self.__translators[dt](ses[dt])
		member.add_data(data)
		return(OK)

	@gen.coroutine
	def __calculate_sub_rating(self, data_type, session_dat, user_dat):
		"""Calculate trust score for specific subtype of user data"""
		sub_tot = 0
		if data_type in user_dat and data_type in session_dat:
			for h in user_dat[data_type]: #loop through all the user's hashed data of this type and compare it to the session
				temp = (yield self.__comparers[data_type](
					session_dat[data_type], h))
				if temp > sub_tot: #fix security issue created by the previous ability to combine multiple low scores
					sub_tot = temp
		elif data_type not in session_dat and data_type in user_dat: #the user's session data may have expired or have not been collected
			sub_tot = -1*self.__maxscores[data_type] #score nonexistant data negativly
		return(sub_tot)

	@gen.coroutine
	def get_trust(self, session_dat, user_dat):
		"""This scores how trustworthy a user is as a number between -1 and 1.
		The score is based on how much session data matches the data stored in their user profile.
		A score of 1 means that the user is perfectly trustworthy, a score of 0 means they cannot be trusted.
		A negative score means that the session data has expired and an accurate determination cannot be made.
		"""
		total = 0 #total user score
		actmax = 0 #maximum achievable total (A user with this score is PERFECT)
		for dt in self.__comparers.keys(): #loop through all the types of data
			actmax += self.__maxscores[dt] #add data type's max score to the maximum
			total += yield self.__calculate_sub_rating(dt, session_dat, user_dat) #calculate sub score for given data type and add it to total
		try:
			return(200, str(total/actmax))
		except ZeroDivisionError:
			logger.critical('This server does not have any client data collection and analysis modules installed')
			return(501, 'This server does not have any data collection and analysis modules installed')

# The primary purpose of this module is to run data hashing and comparison functions
# It is also called during the intialization of modules to register their hashing and comparison functions.
# Try to leave all the boilerplate calls like DB access and JSON decoding in the main.py file

import hijackingprevention.session as session
import hijackingprevention.api_user as api_user

from tornado import gen

import logging
logger = logging.getLogger(__name__)

OK = (200, 'OK')

class Receiver():
	"""This class handles all requests to hash and interpret data.
	Most of the shared functionalty that underpins the API is defined here.
	Functions in here fall into two catagories:
		Functions that allow modules to register hashers or comparers.
		Functions that run the registered hashers or comparers.
	Please note:
		If a function you are considering including in this file does not invoke a hasher, translator, or comparer it may not belong in this file.
	"""
	def __init__(self):
		self.__hashers = {}
		self.__translators = {}
		self.__comparers = {}
		self.__maxscores = {}
		self.__callbacks = {}

	# Module registration functions

	def add_hasher(self, name, fxn):
		"""This is called by each module on startup to register its sitewide hasher."""
		self.__hashers[name] = fxn

	def add_translator(self, name, fxn):
		"""This is called by each module on startup to register its data translator.
		Translators hash data, which has already been hashed by a (sitewide) hasher, with a per user salt.
		"""
		self.__translators[name] = fxn

	def add_comparer(self, name, fxn, score):
		"""This is called by each module on startup to register its hash comparer."""
		self.__comparers[name] = fxn
		self.__maxscores[name] = score

	# Module users and API interface

	@gen.coroutine
	def add_data(self, req, headers, db):
		"""This function is called when data is received from a browser to hash and store the data"""
		# This function has, rather irritatingly, remained in spite of my attempt to keep database access out of the api.py file.  
		if req['name'] in self.__hashers.keys():
			#setup to invoke hasher
			site = api_user.Site(db)
			yield site.get_by_client_key(req['ck'],
				headers.get("Host"))
			salt = site.get_salt(req['name'])
			#invoke hasher
			hash = yield self.__hashers[req['name']](req['data'],
				headers, salt)
			#store the result
			site_id = site.get_id()
			ses = session.Session(req['sid'], site_id, db) #setup session object
			yield ses.read_db() #read session if it exists
			ses.add_data({req['name']:hash}) #add data to session object
			yield ses.write_out() #update session object in database
			return(OK) #Nothing failed. Right? oops.
		else:
			logger.warning('Could not find a handler for "' + req['name'] + '"')
			return(400, 'Err: Could not find a handler for "' + req['name'] + '"')

	@gen.coroutine
	def copy_data(self, ses, member):
		"""This function is called to store session data permenantly to the user profile"""
		data = {}
		for dt in ses.keys():
			data[dt] = yield self.__translators[dt](ses[dt])
		member.add_data(data)
		return(OK)

	@gen.coroutine
	def __calculate_sub_rating(self, data_type, session_dat, user_dat):
		"""Calculate trust score for specific subtype of user data"""
		sub_tot = 0
		if data_type in user_dat and data_type in session_dat:
			for h in user_dat[data_type]: #loop through all the user's hashed data of this type and compare it to the session
				temp = (yield self.__comparers[data_type](
					session_dat[data_type], h))
				if temp > sub_tot: #fix security issue created by the previous ability to combine multiple low scores
					sub_tot = temp
		elif data_type not in session_dat and data_type in user_dat: #the user's session data may have expired or have not been collected
			sub_tot = -1*self.__maxscores[data_type] #score nonexistant data negativly
		return(sub_tot)

	@gen.coroutine
	def get_trust(self, session_dat, user_dat):
		"""This scores how trustworthy a user is as a number between -1 and 1.
		The score is based on how much session data matches the data stored in their user profile.
		A score of 1 means that the user is perfectly trustworthy, a score of 0 means they cannot be trusted.
		A negative score means that the session data has expired and an accurate determination cannot be made.
		"""
		total = 0 #total user score
		actmax = 0 #maximum achievable total (A user with this score is PERFECT)
		for dt in self.__comparers.keys(): #loop through all the types of data
			actmax += self.__maxscores[dt] #add data type's max score to the maximum
			total += yield self.__calculate_sub_rating(dt, session_dat, user_dat) #calculate sub score for given data type and add it to total
		try:
			return(200, str(total/actmax))
		except ZeroDivisionError:
			logger.critical('This server does not have any client data collection and analysis modules installed')
			return(501, 'This server does not have any data collection and analysis modules installed')

# The primary purpose of this module is to run data hashing and comparison functions # It is also called during the intialization of modules to register their hashing and comparison functions. # Try to leave all the boilerplate calls like DB access and JSON decoding in the main.py file This class handles all requests to hash and interpret data. 	Most of the shared functionalty that underpins the API is defined here. 	Functions in here fall into two catagories: 		Functions that allow modules to register hashers or comparers. 		Functions that run the registered hashers or comparers. 	Please note: 		If a function you are considering including in this file does not invoke a hasher, translator, or comparer it may not belong in this file. # Module registration functions This is called by each module on startup to register its sitewide hasher. This is called by each module on startup to register its data translator. 		Translators hash data, which has already been hashed by a (sitewide) hasher, with a per user salt. This is called by each module on startup to register its hash comparer. # Module users and API interface This function is called when data is received from a browser to hash and store the data # This function has, rather irritatingly, remained in spite of my attempt to keep database access out of the api.py file. #setup to invoke hasher #invoke hasher #store the result #setup session object #read session if it exists #add data to session object #update session object in database #Nothing failed. Right? oops. This function is called to store session data permenantly to the user profile Calculate trust score for specific subtype of user data #loop through all the user's hashed data of this type and compare it to the session #fix security issue created by the previous ability to combine multiple low scores #the user's session data may have expired or have not been collected #score nonexistant data negativly This scores how trustworthy a user is as a number between -1 and 1. 		The score is based on how much session data matches the data stored in their user profile. 		A score of 1 means that the user is perfectly trustworthy, a score of 0 means they cannot be trusted. 		A negative score means that the session data has expired and an accurate determination cannot be made. #total user score #maximum achievable total (A user with this score is PERFECT) #loop through all the types of data #add data type's max score to the maximum #calculate sub score for given data type and add it to total