def parse_enum_array(self, key: str, enum_type: Type[Enum], type_name: str, min_elements: Optional[int] = None, max_elements: Optional[int] = None) -> List[Enum]: array = self.require(key, of_type=list, type_name='array') if min_elements is not None: if len(array) < min_elements: raise BadRequest('{k} array minimum elements is {i}'.format( k=key, i=str(min_elements))) pass if len(array) < 1: return array if max_elements is not None: if len(array) < min_elements: raise BadRequest('{k} array maximum length is {i}'.format( k=key, i=str(max_elements))) pass valid_values = [c.value for c in enum_type] for item in array: if item not in valid_values: raise BadRequest('Bad {t} value for enumeration at key {k}. Ac\ ceptable values: {v}'.format(t=type_name, k=key, v=str(valid_values))) continue return [enum_type(i) for i in array]
def _validate_string(self, value: Any, key: str, max_length: Optional[int] = None, min_length: Optional[int] = None, filter_threats: bool = True, allow_whitespace: bool = False) -> str: if not isinstance(value, str): raise BadRequest('Value for key ' + key + ' must be string') if max_length is not None and len(value) > max_length: raise BadRequest(key + ' max length: ' + str(max_length)) if min_length is not None and len(value) < min_length: raise BadRequest(key + ' min length: ' + str(min_length)) if allow_whitespace is False: if True in [c in value for c in string.whitespace]: raise BadRequest('Whitespace not allowed') #if filter_threats is True: # if True in [f in value for f in ()]: # raise BadRequest('Yikes!') return value
def parse_many_strings(self, key: str, max_length: Optional[int] = None, min_length: Optional[int] = None, filter_threats: bool = True, allow_whitespace: bool = False, minimum_count: int = 0, maximum_count: Optional[int] = None) -> List[str]: if not hasattr(self._raw, 'getlist'): raise RuntimeError(self._MULTI_KEY_ERROR) values = self._raw.getlist(key) if len(values) < minimum_count: raise BadRequest('Supply at least {c} value for key {k}'.format( c=str(minimum_count), k=key)) if maximum_count is not None and len(values) > maximum_count: raise BadRequest('Supply less than {c} values for key {k}'.format( c=str(maximum_count), k=key)) for value in values: self._validate_string(value=value, key=key, max_length=max_length, min_length=min_length, filter_threats=filter_threats, allow_whitespace=allow_whitespace) return values
def parse_many_ints(self, key: str, max_value: Optional[int] = None, min_value: Optional[int] = None, minimum_count: int = 0, maximum_count: Optional[int] = None) -> List[int]: if not hasattr(self._raw, 'getlist'): raise RuntimeError(self._MULTI_KEY_ERROR) values = self._raw.getlist(key) if len(values) < minimum_count: raise BadRequest('{k} must provide at least {n} integers'.format( k=key, n=str(minimum_count))) if maximum_count is not None and len(values) > maximum_count: raise BadRequest('{k} must provide no more than {n} \ integers'.format(k=key, n=str(maximum_count))) for value in values: self._validate_integer(key=key, candidate=value, max_value=max_value, min_value=min_value) return values
def parse_integer_array(self, key: str) -> List[int]: value = self.raw.get(key) error = key + ' must be an array of integers' if not isinstance(value, list): raise BadRequest(error) if False in [isinstance(v, int) for v in value]: raise BadRequest(error) return value
def _constrain_number(self, key: str, number: _Number, max_value: Optional[_Number], min_value: Optional[_Number]) -> _Number: if min_value is not None and number < min_value: raise BadRequest(key + ' below mininum value: ' + str(min_value)) if max_value is not None and number > max_value: raise BadRequest(key + ' above maximum value: ' + str(max_value)) return number
def parse_many_strings( self, key: str, max_length: Optional[int] = None, min_length: Optional[int] = None, allow_whitespace: bool = False, minimum_count: int = 0, maximum_count: Optional[int] = None, delimiter: Optional[str] = None, allowed_characters: Optional[str] = None, disallowed_characters: Optional[str] = None) -> List[str]: def derive_multikey_values() -> List[str]: if not hasattr(self._raw, 'getlist'): raise RuntimeError(self._MULTI_KEY_ERROR) return self._raw.getlist(key) def derive_delimited_values(delimiter: str) -> List[str]: return (self.optionally_parse_string(key=key) or '').split(delimiter) def derive_values() -> List[str]: if delimiter is None: return derive_multikey_values() return derive_delimited_values(delimiter) values = derive_values() if len(values) < minimum_count: raise BadRequest('Supply at least {c} value(s) for key {k}'.format( c=str(minimum_count), k=key)) if maximum_count is not None and len(values) > maximum_count: raise BadRequest('Supply less than {c} values for key {k}'.format( c=str(maximum_count), k=key)) for value in values: self._validate_string(value=value, key=key, max_length=max_length, min_length=min_length, allow_whitespace=allow_whitespace, allowed_characters=allowed_characters, disallowed_characters=disallowed_characters) continue return values
def require(self, key: str, of_type: Optional[Type] = None) -> Any: value = self.get(key, of_type) if value is None: raise BadRequest('Missing value for key ' + key) return value
def optionally_many_from_arguments( cls: Type[T], arguments: QueryString, available: Optional[Dict[str, T]] = None, key: str = 'order_by', fallback_to: Optional[T] = None) -> Optional[List[T]]: if available is None: if not isinstance(cls.available, dict): raise RuntimeError('implement .available order by values') available = cls.available values = arguments.parse_string(key).split(',') if values is None or len(values) < 1: return fallback_to parsed: List[T] = [] for term in values: if term not in available.keys(): raise BadRequest('Invalid {k} value. Valid values: {v}'.format( k=key, v=', '.join(available.keys()))) parsed.append(available[term]) continue return parsed
def parse_bool(self, key: str) -> bool: value = self.optionally_parse_bool(key) if value is None: raise BadRequest(key + ' parameter missing') return value
def from_request(cls: Type[T], data: ParseableData, key: str) -> T: time = cls.optionally_from_request(data, key) if time is None: raise BadRequest('Supply a time, in format {f}, under key \ {k}'.format(f=cls._NO_MS_FORMAT, k=key)) return time
def __init__(self, headers: Headers, request_body: bytes, max_content_length: int = 10000) -> None: length = headers.value_for('content-length') if length is None: raise BadRequest('No content-length header found in your request.') try: content_length = int(length) except ValueError: raise BadRequest('Invalid content-length header value') if content_length > max_content_length: raise NozomiError( 'Content too large, max: {s}'.format( s=str(max_content_length)), HTTPStatusCode.PAYLOAD_TOO_LARGE.value) try: string_data = request_body.decode('utf-8') except Exception: raise BadRequest('Unable to decode the body of your request with t\ he UTF-8 character set. UTF-8 required.') content_type = headers.value_for('content-type') if content_type is None: raise BadRequest('Could not find a content-type header in your req\ uest.') try: content_type_value = content_type.lower().split(';')[0] except Exception: raise BadRequest('Unable to parse your request\'s content-type hea\ der.') if content_type_value == 'application/json': try: json_data = json.loads(string_data) except Exception: raise BadRequest('Unable to parse your request body as json. P\ lease check the syntax of your request body.') return super().__init__(raw=json_data) if content_type_value == 'application/xml': try: xml_data = XML.xmlstring_to_data(string_data) except Exception: raise BadRequest('Unable to parse your request body as xml. Pl\ ease check the syntax of your request body.') return super().__init__(raw=xml_data) raise BadRequest('Invalid content type. Valid content types are applic\ ation/json and application/xml only.')
def get(self, key: str, of_type: Optional[Type] = None) -> Optional[Any]: if key not in self._raw.keys(): return None value = self._raw[key] if of_type is not None and not isinstance(value, of_type): raise BadRequest('Value for key ' + key + ' has incorrect type') return value
def _validate_float(self, key: str, candidate: Any, max_value: Optional[float] = None, min_value: Optional[float] = None) -> float: if isinstance(candidate, bool): raise BadRequest(key + ' must be float or string encoded float') try: float_value = float(candidate) except Exception: raise BadRequest(key + ' must be float or string encoded float') return self._constrain_number(key=key, number=float_value, max_value=max_value, min_value=min_value)
def from_arguments(cls: Type[T], arguments: QueryString, available: Optional[Dict[str, T]] = None, key: str = 'order_by') -> T: order_by = cls.optionally_from_arguments(arguments, available, key) if order_by is None: raise BadRequest('Missing value for ' + key) return order_by
def _validate_integer(self, key: str, candidate: Any, max_value: Optional[int] = None, min_value: Optional[int] = None) -> int: try: integer_value = int(candidate) except Exception: raise BadRequest(key + ' must be integer or string encoded integer') if min_value is not None and integer_value < min_value: raise BadRequest(key + ' below mininum value: ' + str(min_value)) if max_value is not None and integer_value > max_value: raise BadRequest(key + ' above maximum value: ' + str(max_value)) return integer_value
def from_request(cls: Type[T], request_data: ParseableData, default_to: Optional[T] = None) -> T: order = cls.optionally_from_request(request_data, default_to) if order is None: raise BadRequest('Supply order=[ascending|descending] parameter') return order
def from_arguments(cls: Type[T], arguments: QueryString, default_to_descending: bool = False) -> T: order = arguments.optionally_parse_string(key='order', max_length=32) if order is None: if default_to_descending is False: raise BadRequest( 'Supply order=[ascending|descending] parameter') return cls(True) if order.lower() == 'ascending': return cls(True) if order.lower() == 'descending': return cls(False) raise BadRequest('Acceptable `order` values: ascending, descending')
def _validate_integer(self, key: str, candidate: Any, max_value: Optional[int] = None, min_value: Optional[int] = None) -> int: if isinstance(candidate, bool): raise BadRequest(key + ' must be integer or string encoded integer') try: integer_value = int(candidate) except Exception: raise BadRequest(key + ' must be integer or string encoded integer') return self._constrain_number(key=key, number=integer_value, max_value=max_value, min_value=min_value)
def parse_enum(self, key: str, enum_type: Type[Enum], type_name: str) -> Optional[Enum]: value = self.optionally_parse_enum(key=key, enum_type=enum_type, type_name=type_name) if value is not None: return value raise BadRequest('Missing {k} parameter'.format(k=key))
def optionally_from_request(cls: Type[T], data: ParseableData, key: str) -> Optional[T]: raw_time = data.optionally_parse_string(key, allow_whitespace=True) if raw_time is None: return None try: time = cls.decode(raw_time) except ValueError: raise BadRequest( 'Time must be in the format {f}'.format(f=cls._NO_MS_FORMAT)) return time
def parse_int(self, key: str, max_value: Optional[int] = None, min_value: Optional[int] = None) -> int: value = self.optionally_parse_int(key, max_value=max_value, min_value=min_value) if value is None: raise BadRequest('Missing ' + key + ' parameter') return value
def optionally_parse_decimal( self, key: str, max_value: Optional[Decimal] = None, min_value: Optional[Decimal] = None) -> Optional[Decimal]: value = self._raw.get(key) if value is None: return None try: decimal_value = Decimal(value) except Exception: raise BadRequest(key + ' must be a string encoded decimal') if min_value is not None and decimal_value < min_value: raise BadRequest(key + ' below mininum value: ' + str(min_value)) if max_value is not None and decimal_value > max_value: raise BadRequest(key + ' above maximum value: ' + str(max_value)) return decimal_value
def get(self, key: str, of_type: Optional[Type] = None, type_name: Optional[str] = None) -> Optional[Any]: if key not in self._raw.keys(): return None value = self._raw[key] if of_type is not None and not isinstance(value, of_type): if of_type == int and isinstance(value, str): try: int_value = int(value) return int_value except Exception: pass if type_name is None: raise BadRequest('Value for key ' + key + ' has incorrect type') raise BadRequest('Value for key {k} must be {t}'.format( k=key, t=type_name)) return value
def create_with_request_data(cls: Type[T], data: Any, ip_address: IpAddress, datastore: Datastore, perspective: Perspective) -> T: """Return a newly minted Session""" assert isinstance(ip_address, IpAddress) assert isinstance(perspective, Perspective) if not isinstance(data, dict): raise BadRequest('Expected a key/value object (dict)') try: provided_plaintext_secret = data['secret'] provided_email = data['email'] except KeyError as error: raise BadRequest('Missing key ') raise NozomiError('Missing key ' + str(error.args[0]), 400) return cls.create(provided_email=provided_email, provided_plaintext_secret=provided_plaintext_secret, ip_address=ip_address, datastore=datastore, perspective=perspective)
def parse_decimal(self, key: str, max_value: Optional[Decimal] = None, min_value: Optional[Decimal] = None) -> Decimal: decimal = self.optionally_parse_decimal(key=key, max_value=max_value, min_value=min_value) if decimal is None: raise BadRequest(key + ' missing string encoded decimal for key \ `{k}`'.format(k=key)) return decimal
def parse_from_request(cls: Type[T], data: Any) -> T: if not isinstance(data, str): raise BadRequest('Date must be a string') try: date = cls.strptime(data, cls._REQUEST_FORMAT_STRING) return cls(year=date.year, month=date.month, day=date.day) except Exception: pass # Give another format a go try: date = cls.strptime(data, cls._REQUEST_FORMAT_STRING_B) return cls(year=date.year, month=date.month, day=date.day) except Exception: pass # Give another format a go try: date = cls.strptime(data, cls._REQUEST_FORMAT_STRING_C) return cls(year=date.year, month=date.month, day=date.day) except Exception: pass # Give another format a go try: date = cls.strptime(data, cls._REQUEST_FORMAT_STRING_D) return cls(year=date.year, month=date.month, day=date.day) except Exception: raise BadRequest('Date format unrecognised, expected {fmt}'.format( fmt=cls._REQUEST_FORMAT_STRING))
def parse_string_array(self, key: str, max_length: Optional[int] = None, min_length: Optional[int] = None, allow_whitespace: bool = False, allowed_characters: Optional[str] = None, disallowed_characters: Optional[str] = None, min_elements: Optional[int] = None, max_elements: Optional[int] = None) -> List[str]: array = self.require(key=key, of_type=list, type_name='array') if min_elements is not None: if len(array) < min_elements: raise BadRequest('{k} array minimum elements is {i}'.format( k=key, i=str(min_elements))) if len(array) < 1: return array if max_elements is not None: if len(array) < min_elements: raise BadRequest('{k} array maximum length is {i}'.format( k=key, i=str(max_elements))) for item in array: self._validate_string(value=item, key=key, max_length=max_length, min_length=min_length, allow_whitespace=allow_whitespace, allowed_characters=allowed_characters, disallowed_characters=disallowed_characters) continue return array
def optionally_parse_bool(self, key: str) -> Optional[bool]: value = self.get(key) if value is None: return None if isinstance(value, bool): return value if value == 'true': return True if value == 'false': return False raise BadRequest(key + ' must be "true" or "false"')
def optionally_parse_enum(self, key: str, enum_type: Type[Enum], type_name: str) -> Optional[Enum]: value = self.get(key, of_type=type([v.value for v in enum_type][0])) if value is None: return None try: result = enum_type(value) except ValueError: raise BadRequest('Bad {t} value for enumeration at key {k}. Accept\ able values: {v}'.format(t=type_name, k=key, v=str([v.value for v in enum_type]))) return result