예제 #1
0
    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
예제 #6
0
    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
예제 #7
0
    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
예제 #9
0
    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
예제 #11
0
    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
예제 #12
0
    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
예제 #14
0
    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)
예제 #15
0
    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
예제 #17
0
    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
예제 #18
0
    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')
예제 #19
0
    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)
예제 #20
0
    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))
예제 #21
0
    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
예제 #24
0
    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
예제 #25
0
    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
예제 #27
0
 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))
예제 #28
0
    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"')
예제 #30
0
    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