def get(self, username_from_url=None): username_from_url = username_from_url.decode('utf8') username_normalized = username_from_url.lower() if username_normalized: logging.info('Query for username: '******'usernameNormalized': username_normalized, 'usernameAvailable': False, } logging.info('Username taken: ' + repr(user_obj)) else: response_dict = { 'usernameNormalized': username_normalized, 'usernameAvailable': True, } logging.info('Username is available: ' + username_normalized) http_helpers.set_http_ok_json_response(self.response, response_dict) else: err_status = 'ErrorUsernameRequired' # log this error for further analysis status_reporting.log_call_stack_and_traceback(logging.error, extra_info = err_status) http_helpers.set_http_error_json_response(self.response, err_status)
def _get_current_presence_state_from_database(self): try: client_id = self.key.id() # Make sure that the value stored in the database was recently written, and if not then # the user is considered to be offline if (self.last_db_write + datetime.timedelta( seconds=DATABASE_PRESENCE_UPDATE_INTERVAL_TIMEOUT_SECONDS) < datetime.datetime.utcnow()): logging.debug( 'Client %s is set to PRESENCE_OFFLINE due to timeout (database check)' % client_id) return 'PRESENCE_OFFLINE' else: logging.info( 'Presence state of %s retrieved from database for client %s' % (self.most_recent_presence_state_stored_in_db, self.key.id())) return self.most_recent_presence_state_stored_in_db except: err_message = '_get_current_presence_state_from_database returning PRESENCE_OFFLINE due to internal error' status_reporting.log_call_stack_and_traceback( logging.error, extra_info=err_message) return 'PRESENCE_OFFLINE'
def get_jwt_token_payload_and_token_session_obj(authorization_header): token_payload = None token_session_obj = None if authorization_header: (bearer_txt, split_char, token_string) = authorization_header.partition(' ') try: token_payload = jwt.decode(token_string, constants.secret_key) token_session_id = token_payload['jti'] token_session_obj = TokenSessionModel.get_by_id(token_session_id) if token_session_obj: # This token has been found in the database if token_session_obj.token_expiration_datetime < datetime.datetime.utcnow(): # This token has expired. Set it to none token_session_obj = None else: raise LookupError('Session not found in the database: %s' % token_payload) except: # For some reason this token has triggered an error - may require more investigation extra_info = "token_string: %s" % token_string status_reporting.log_call_stack_and_traceback(logging.warning, extra_info=extra_info) return token_payload, token_session_obj
def get_jwt_token_payload_and_token_session_obj(authorization_header): token_payload = None token_session_obj = None if authorization_header: (bearer_txt, split_char, token_string) = authorization_header.partition(' ') try: token_payload = jwt.decode(token_string, constants.secret_key) token_session_id = token_payload['jti'] token_session_obj = TokenSessionModel.get_by_id(token_session_id) if token_session_obj: # This token has been found in the database if token_session_obj.token_expiration_datetime < datetime.datetime.utcnow( ): # This token has expired. Set it to none token_session_obj = None else: raise LookupError('Session not found in the database: %s' % token_payload) except: # For some reason this token has triggered an error - may require more investigation extra_info = "token_string: %s" % token_string status_reporting.log_call_stack_and_traceback( logging.warning, extra_info=extra_info) return token_payload, token_session_obj
def get(self, chat_room_name_from_url=None): chat_room_name_from_url = chat_room_name_from_url.decode('utf8') chat_room_name_normalized = chat_room_name_from_url.lower() if chat_room_name_normalized: logging.info('Query for room name: ' + chat_room_name_normalized) chat_room_obj = ChatRoomModel.query(ChatRoomModel.chat_room_name_normalized == chat_room_name_normalized).get() if chat_room_obj: response_dict = { 'chatRoomName': chat_room_name_normalized, 'roomIsRegistered': True, 'numInRoom': chat_room_obj.get_occupancy(), } logging.info('Found room: ' + repr(chat_room_obj)) else: response_dict = { 'chatRoomName': chat_room_name_normalized, 'roomIsRegistered' : False, 'numInRoom': 0 } logging.info('Room name is available: ' + chat_room_name_normalized) http_helpers.set_http_ok_json_response(self.response, response_dict) else: err_status = 'ErrorChatRoomNameRequired' # log this error for further analysis status_reporting.log_call_stack_and_traceback(logging.error, extra_info = err_status) http_helpers.set_http_error_json_response(self.response, err_status)
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: status_string = "serverError" request = None # Check if this is a method on a RequestHandler object, and if so we also write # out an error to the http response. # Note: this code is only executed if an error occurs, and therefore we don't worry # about the extra "cost" of executing the code below since it should be rarely executed. arg_spec = inspect.getargspec(func) if arg_spec.args and arg_spec.args[0] == 'self': # if the first parameter is 'self' then it is likely that this is a method on an object self = args[0] if hasattr(self, '__class__'): # if self has an attrute '__class__' this self is likely a parameter on a method object # as opposed to a parameter on a function cls = self.__class__ if issubclass(cls, webapp2.RequestHandler): # if cls is a subclass of RequestHander, then we are pretty sure that the user is generating http on the # self.response object if hasattr(self, 'response'): # If self has a 'response' attribute, then write out the error_status to self.response. request = self.request logging.debug('executing set_http_error_json_response self.response=%s and status_string=%s' % (repr(self.response), status_string)) http_helpers.set_http_error_json_response(self.response, status_string, 500) # Log the error to the server, along with stack trace and debugging information logging.debug('executing log_call_stack_and_traceback with extra_info = %s' % status_string) status_reporting.log_call_stack_and_traceback(logging.error, extra_info = status_string, request = request)
def txn_add_client_to_room(self, client_id): room_key = self.key new_client_has_been_added = False logging.info('Adding client %s to room %s ' % (client_id, room_key)) chat_room_obj = room_key.get() if chat_room_obj: try: # If user is not already in the room, then add them if not client_id in chat_room_obj.room_members_client_ids: chat_room_obj.room_members_client_ids.append(client_id) chat_room_obj.put() logging.info('Client %s has joined room %s ' % (client_id, room_key)) new_client_has_been_added = True else: logging.info('Client %s was already in room %s ' % (client_id, room_key)) # If the user cannot be added to the room, then an exception will be generated - let the client # know that the server had a problem. except: status_reporting.log_call_stack_and_traceback(logging.error) raise Exception('Error adding client %s to room %s' % (client_id, room_key)) else: error_message = 'Invalid room id: %s' % room_key logging.error(error_message) raise Exception(error_message) return (new_client_has_been_added, chat_room_obj)
def post(self): try: room_dict = json.loads(self.request.body) # Need to get the URL encoded data from utf8. Note that json encoded data appears to already be decoded. # Convert camel case keys to underscore (standard python) keys room_dict = utils.convert_dict(room_dict, utils.camel_to_underscore) chat_room_name_as_written = room_dict['chat_room_name_as_written'] chat_room_name_normalized = chat_room_name_as_written.lower() room_dict['chat_room_name_normalized'] = chat_room_name_normalized # Make sure that the room name is valid before continuing. # These errors should be rare since these values are # already formatted to be within bounds and characters checked by the client-side javascript. # Only if the user has somehow bypassed the javascript checks should we receive values that don't # conform to the constraints that we have placed. if not constants.valid_chat_room_name_regex_compiled.match(chat_room_name_as_written): raise Exception('Room name regexp did not match') if len(chat_room_name_as_written) > constants.room_max_chars or len(chat_room_name_as_written) < constants.room_min_chars: raise Exception('Room name length of %s is out of range' % len(chat_room_name_as_written)) response_dict = {} user_id = int(room_dict['user_id']) assert self.session.user_id == user_id # If this is a new room, then the room_creator_user_key will be stored in the room # object as the "creator" of the room room_creator_user_key = ndb.Key('UserModel', user_id) chat_room_obj = ChatRoomModel.create_or_get_room(chat_room_name_normalized, room_dict, room_creator_user_key) response_dict['chatRoomNameNormalized'] = chat_room_name_normalized response_dict['chatRoomId'] = chat_room_obj.key.id() response_dict['statusString'] = 'roomJoined' http_helpers.set_http_ok_json_response(self.response, response_dict) except: # Unknown exception - provide feedback to the user to indicate that the room was not created or entered into err_status = 'ErrorUnableToCreateOrEnterRoom' # log this error for further analysis status_reporting.log_call_stack_and_traceback(logging.error, extra_info = err_status) http_helpers.set_http_ok_json_response(self.response, {'statusString': err_status})
def _get_current_presence_state_from_database(self): try: client_id = self.key.id() # Make sure that the value stored in the database was recently written, and if not then # the user is considered to be offline if (self.last_db_write + datetime.timedelta(seconds=DATABASE_PRESENCE_UPDATE_INTERVAL_TIMEOUT_SECONDS) < datetime.datetime.utcnow()): logging.debug('Client %s is set to PRESENCE_OFFLINE due to timeout (database check)' % client_id) return 'PRESENCE_OFFLINE' else: logging.info('Presence state of %s retrieved from database for client %s' % (self.most_recent_presence_state_stored_in_db, self.key.id())) return self.most_recent_presence_state_stored_in_db except: err_message = '_get_current_presence_state_from_database returning PRESENCE_OFFLINE due to internal error' status_reporting.log_call_stack_and_traceback(logging.error, extra_info = err_message) return 'PRESENCE_OFFLINE'
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: status_string = "serverError" request = None # Check if this is a method on a RequestHandler object, and if so we also write # out an error to the http response. # Note: this code is only executed if an error occurs, and therefore we don't worry # about the extra "cost" of executing the code below since it should be rarely executed. arg_spec = inspect.getargspec(func) if arg_spec.args and arg_spec.args[0] == 'self': # if the first parameter is 'self' then it is likely that this is a method on an object self = args[0] if hasattr(self, '__class__'): # if self has an attrute '__class__' this self is likely a parameter on a method object # as opposed to a parameter on a function cls = self.__class__ if issubclass(cls, webapp2.RequestHandler): # if cls is a subclass of RequestHander, then we are pretty sure that the user is generating http on the # self.response object if hasattr(self, 'response'): # If self has a 'response' attribute, then write out the error_status to self.response. request = self.request logging.debug( 'executing set_http_error_json_response self.response=%s and status_string=%s' % (repr(self.response), status_string)) http_helpers.set_http_error_json_response( self.response, status_string, 500) # Log the error to the server, along with stack trace and debugging information logging.debug( 'executing log_call_stack_and_traceback with extra_info = %s' % status_string) status_reporting.log_call_stack_and_traceback( logging.error, extra_info=status_string, request=request)
def handle_500(request, response, exception): status_reporting.log_call_stack_and_traceback(logging.error) set_error_json_response_and_write_log(response, 'handle_500 - Server error occured. Path: %s' % request.path, logging.error, 500)
def handle_404(request, response, exception): status_reporting.log_call_stack_and_traceback(logging.error) set_error_json_response_and_write_log(response, 'handle_404 - Page not found: %s' % request.path, logging.error, 404)
def set_error_json_response_and_write_log(response, status_string, logging_function, http_status_code, request=None): status_reporting.log_call_stack_and_traceback(logging_function, extra_info = status_string, request=request) set_http_error_json_response(response, status_string , http_status_code)
def dispatch(self): error_key = 'unknownError' error_message = 'Unknown Error' try: authorization_header = self.request.headers.environ.get('HTTP_AUTHORIZATION') # Get the "now" before the session, so that in the case that the session is not expired, # "now" is always <= session expiry. If we were to get "now" after getting the token # then this inequality might not be valid in rare cases where the token has just expired # within the past few milliseconds. now = datetime.datetime.now() token_payload, token_session_obj = token_sessions.get_jwt_token_payload_and_token_session_obj(authorization_header) if token_payload and token_session_obj: # This session is still valid. Continue processing. self.session.user_id = token_payload['userId'] self.session.username_as_written = token_payload['usernameAsWritten'] logging.info('******** SESSION user_id: %s username: %s ********' % (self.session.user_id, self.session.username_as_written)) if self.verify_client: logging.info('*** Verifying client information') if self.request.method == 'POST': # We expect all POSTs to contain a json object that contains a clientId post_body_json = json.loads(self.request.body) self.session.post_body_json = post_body_json client_id = None if 'clientId' in post_body_json: client_id = post_body_json['clientId'] assert self.session.user_id == int(client_id.split('|')[0]) client_obj = ndb.Key('ClientModel', client_id).get() self.session.client_obj = client_obj else: client_obj = None if not client_obj: error_key = 'invalidClientId' error_message = 'Client object not found for clientId %s. Request denied. ' % client_id raise Exception(error_message) user_id = self.session.user_id user_obj = users.UserModel.get_by_id(user_id) self.session.user_obj = user_obj if not user_obj: error_key = 'invalidUserAuthToken' error_message = 'User object not found for userId %s. Request denied. ' % user_id raise Exception(error_message) session_expiry = token_session_obj.token_expiration_datetime # The session should not already be expired assert(session_expiry >= now) if (session_expiry - now < datetime.timedelta(seconds = constants.seconds_before_expiration_to_refresh_token)): # The session will expire soon. However, because the client is still connected to the server # we grant them an extension on their session. token_session_obj.token_expiration_datetime = user_obj.get_token_expiration_datetime() logging.info('**** Session expiration date is being reset to %s' % token_session_obj.token_expiration_datetime) token_session_obj.put() if not user_obj.registered_user_bool: # only update the token_expiration_datetime on the userobject in the case that this is a non-registered # user. For registered users, this value is irrelevant and should be set to None, as the # user object will never expire (however, note that tokens for registered users will still # have expiry dates). user_obj.txn_update_expiration_datetime(token_session_obj.token_expiration_datetime) # Dispatch the request. webapp2.RequestHandler.dispatch(self) else: error_key = 'invalidUserAuthToken' error_message = 'Invalid or expired token with payload %s. Request denied. Header: %s' % \ (token_payload, authorization_header) raise Exception(error_message) except: # This client/user has been denied access. # Send unauthorized 401 code as an error response. status_reporting.log_call_stack_and_traceback(logging.warn, extra_info = error_message) http_helpers.set_http_json_response(self.response, {error_key: error_message}, 401)
def dispatch(self): error_key = 'unknownError' error_message = 'Unknown Error' try: authorization_header = self.request.headers.environ.get( 'HTTP_AUTHORIZATION') # Get the "now" before the session, so that in the case that the session is not expired, # "now" is always <= session expiry. If we were to get "now" after getting the token # then this inequality might not be valid in rare cases where the token has just expired # within the past few milliseconds. now = datetime.datetime.now() token_payload, token_session_obj = token_sessions.get_jwt_token_payload_and_token_session_obj( authorization_header) if token_payload and token_session_obj: # This session is still valid. Continue processing. self.session.user_id = token_payload['userId'] self.session.username_as_written = token_payload[ 'usernameAsWritten'] logging.info( '******** SESSION user_id: %s username: %s ********' % (self.session.user_id, self.session.username_as_written)) if self.verify_client: logging.info('*** Verifying client information') if self.request.method == 'POST': # We expect all POSTs to contain a json object that contains a clientId post_body_json = json.loads(self.request.body) self.session.post_body_json = post_body_json client_id = None if 'clientId' in post_body_json: client_id = post_body_json['clientId'] assert self.session.user_id == int( client_id.split('|')[0]) client_obj = ndb.Key('ClientModel', client_id).get() self.session.client_obj = client_obj else: client_obj = None if not client_obj: error_key = 'invalidClientId' error_message = 'Client object not found for clientId %s. Request denied. ' % client_id raise Exception(error_message) user_id = self.session.user_id user_obj = users.UserModel.get_by_id(user_id) self.session.user_obj = user_obj if not user_obj: error_key = 'invalidUserAuthToken' error_message = 'User object not found for userId %s. Request denied. ' % user_id raise Exception(error_message) session_expiry = token_session_obj.token_expiration_datetime # The session should not already be expired assert (session_expiry >= now) if (session_expiry - now < datetime.timedelta( seconds=constants. seconds_before_expiration_to_refresh_token)): # The session will expire soon. However, because the client is still connected to the server # we grant them an extension on their session. token_session_obj.token_expiration_datetime = user_obj.get_token_expiration_datetime( ) logging.info( '**** Session expiration date is being reset to %s' % token_session_obj.token_expiration_datetime) token_session_obj.put() if not user_obj.registered_user_bool: # only update the token_expiration_datetime on the userobject in the case that this is a non-registered # user. For registered users, this value is irrelevant and should be set to None, as the # user object will never expire (however, note that tokens for registered users will still # have expiry dates). user_obj.txn_update_expiration_datetime( token_session_obj.token_expiration_datetime) # Dispatch the request. webapp2.RequestHandler.dispatch(self) else: error_key = 'invalidUserAuthToken' error_message = 'Invalid or expired token with payload %s. Request denied. Header: %s' % \ (token_payload, authorization_header) raise Exception(error_message) except: # This client/user has been denied access. # Send unauthorized 401 code as an error response. status_reporting.log_call_stack_and_traceback( logging.warn, extra_info=error_message) http_helpers.set_http_json_response(self.response, {error_key: error_message}, 401)
def handle_500(request, response, exception): status_reporting.log_call_stack_and_traceback(logging.error) set_error_json_response_and_write_log( response, "handle_500 - Server error occured. Path: %s" % request.path, logging.error, 500 )
def handle_404(request, response, exception): status_reporting.log_call_stack_and_traceback(logging.error) set_error_json_response_and_write_log( response, "handle_404 - Page not found: %s" % request.path, logging.error, 404 )
def set_error_json_response_and_write_log(response, status_string, logging_function, http_status_code, request=None): status_reporting.log_call_stack_and_traceback(logging_function, extra_info=status_string, request=request) set_http_error_json_response(response, status_string, http_status_code)