def test_valid_args_do_not_raises_exception(self) -> None: # List of 3-tuples, where the first element is a valid argument dict, # the second element is a schema dict and the third element is the # normalized value of the corresponding argument. list_of_valid_args_with_schmea = [({}, { 'exploration_id': { 'schema': { 'type': 'basestring' }, 'default_value': None } }, {}), ({}, { 'exploration_id': { 'schema': { 'type': 'basestring' }, 'default_value': 'default_exp_id' } }, { 'exploration_id': 'default_exp_id' }), ({ 'exploration_id': 'any_exp_id' }, { 'exploration_id': { 'schema': { 'type': 'basestring' } } }, { 'exploration_id': 'any_exp_id' }), ({ 'apply_draft': 'true' }, { 'apply_draft': { 'schema': { 'type': 'bool' } } }, { 'apply_draft': True })] for handler_args, handler_args_schema, normalized_value_for_args in ( list_of_valid_args_with_schmea): normalized_value, errors = payload_validator.validate( handler_args, handler_args_schema, allowed_extra_args=False, allow_string_to_bool_conversion=True) self.assertEqual(normalized_value, normalized_value_for_args) self.assertEqual(errors, [])
def test_invalid_args_raises_exceptions(self) -> None: # List of 3-tuples, where the first element is an invalid argument dict, # the second element is a schema dict and the third element # is a list of errors. list_of_invalid_args_with_schema_and_errors = [ ({ 'exploration_id': 2 }, { 'exploration_id': { 'schema': { 'type': 'basestring' } } }, [ 'Schema validation for \'exploration_id\' failed: ' 'Expected string, received 2' ]), ({ 'version': 'random_string' }, { 'version': { 'schema': { 'type': 'int' } } }, [ 'Schema validation for \'version\' failed: ' 'Could not convert str to int: random_string' ]), ({ 'exploration_id': 'any_exp_id' }, {}, ['Found extra args: [\'exploration_id\'].']), ({}, { 'exploration_id': { 'schema': { 'type': 'basestring' } } }, ['Missing key in handler args: exploration_id.']) ] for handler_args, handler_args_schema, error_msg in ( list_of_invalid_args_with_schema_and_errors): normalized_value, errors = payload_validator.validate( handler_args, handler_args_schema, allowed_extra_args=False, allow_string_to_bool_conversion=False) self.assertEqual(normalized_value, {}) self.assertEqual(error_msg, errors)
def test_valid_args_do_not_raises_exception(self): # type: () -> None # List of 3-tuples, where the first element is a valid argument dict, # the second element is a schema dict and the third element is the # normalized value of the corresponding argument. list_of_valid_args_with_schmea = [({}, { 'exploration_id': { 'schema': { 'type': 'basestring' }, 'default_value': None } }, {}), ({}, { 'exploration_id': { 'schema': { 'type': 'basestring' }, 'default_value': 'default_exp_id' } }, { 'exploration_id': 'default_exp_id' }), ({ 'exploration_id': 'any_exp_id' }, { 'exploration_id': { 'schema': { 'type': 'basestring' } } }, { 'exploration_id': 'any_exp_id' })] for handler_args, handler_args_schema, normalized_value_for_args in ( list_of_valid_args_with_schmea): normalized_value_for_args, errors = payload_validator.validate( handler_args, handler_args_schema, False) self.assertEqual(normalized_value_for_args, normalized_value_for_args) self.assertEqual(errors, [])
def vaidate_and_normalize_args(self): """Validates schema for controller layer handler class arguments. Raises: InvalidInputException. Schema validation failed. NotImplementedError. Schema is not provided in handler class. """ handler_class_name = self.__class__.__name__ request_method = self.request.environ['REQUEST_METHOD'] url_path_args = self.request.route_kwargs handler_class_names_with_no_schema = ( payload_validator.HANDLER_CLASS_NAMES_WITH_NO_SCHEMA) if handler_class_name in handler_class_names_with_no_schema: return handler_args = {} payload_arg_keys = [] request_arg_keys = [] for arg in self.request.arguments(): if arg == 'csrf_token': # 'csrf_token' has been already validated in the # dispatch method. continue elif arg == 'source': source_url = self.request.get('source') regex_pattern = ( r'http[s]?://(?:[a-zA-Z]|[0-9]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+' # pylint: disable=line-too-long ) regex_verified_url = re.findall(regex_pattern, source_url) if not regex_verified_url: raise self.InvalidInputException( 'Not a valid source url.') elif arg == 'payload': payload_args = self.payload if payload_args is not None: payload_arg_keys = list(payload_args.keys()) handler_args.update(payload_args) else: request_arg_keys.append(arg) handler_args[arg] = self.request.get(arg) # For html handlers, extra args are allowed (to accommodate # e.g. utm parameters which are not used by the backend but # needed for analytics). extra_args_are_allowed = ( self.GET_HANDLER_ERROR_RETURN_TYPE == 'html' and request_method == 'GET') if self.URL_PATH_ARGS_SCHEMAS is None: raise NotImplementedError( 'Missing schema for url path args in %s handler class.' % ( handler_class_name)) schema_for_url_path_args = self.URL_PATH_ARGS_SCHEMAS normalized_arg_values, errors = ( payload_validator.validate( url_path_args, schema_for_url_path_args, extra_args_are_allowed) ) if errors: raise self.InvalidInputException('\n'.join(errors)) # This check ensures that if a request method is not defined # in the handler class then schema validation will not raise # NotImplementedError for that corresponding request method. if request_method in ['GET', 'POST', 'PUT', 'DELETE'] and ( getattr(self.__class__, request_method.lower()) == getattr(BaseHandler, request_method.lower())): return try: schema_for_request_method = self.HANDLER_ARGS_SCHEMAS[ request_method] except Exception: raise NotImplementedError( 'Missing schema for %s method in %s handler class.' % ( request_method, handler_class_name)) normalized_arg_values, errors = ( payload_validator.validate( handler_args, schema_for_request_method, extra_args_are_allowed) ) self.normalized_payload = { arg: normalized_arg_values.get(arg) for arg in payload_arg_keys } self.normalized_request = { arg: normalized_arg_values.get(arg) for arg in request_arg_keys } # The following keys are absent in request/payload but present in # normalized_arg_values because these args are populated from their # default_value provided in the schema. keys_that_correspond_to_default_values = list( set(normalized_arg_values.keys()) - set(payload_arg_keys + request_arg_keys) ) # Populate the payload/request with the default args before passing # execution onwards to the handler. for arg in keys_that_correspond_to_default_values: if request_method in ['GET', 'DELETE']: self.normalized_request[arg] = normalized_arg_values.get(arg) else: self.normalized_payload[arg] = normalized_arg_values.get(arg) if errors: raise self.InvalidInputException('\n'.join(errors))