2021-02-12 08:20:45 +01:00
|
|
|
"""
|
2013-12-12 23:47:12 +01:00
|
|
|
This module sets up a scheme for validating that arbitrary Python
|
|
|
|
objects are correctly typed. It is totally decoupled from Django,
|
|
|
|
composable, easily wrapped, and easily extended.
|
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
A validator takes two parameters--var_name and val--and raises an
|
2013-12-12 23:47:12 +01:00
|
|
|
error if val is not the correct type. The var_name parameter is used
|
2020-06-21 02:36:20 +02:00
|
|
|
to format error messages. Validators return the validated value when
|
|
|
|
there are no errors.
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
Example primitive validators are check_string, check_int, and check_bool.
|
|
|
|
|
|
|
|
Compound validators are created by check_list and check_dict. Note that
|
|
|
|
those functions aren't directly called for validation; instead, those
|
|
|
|
functions are called to return other functions that adhere to the validator
|
|
|
|
contract. This is similar to how Python decorators are often parameterized.
|
|
|
|
|
|
|
|
The contract for check_list and check_dict is that they get passed in other
|
|
|
|
validators to apply to their items. This allows you to build up validators
|
|
|
|
for arbitrarily complex validators. See ValidatorTestCase for example usage.
|
|
|
|
|
|
|
|
A simple example of composition is this:
|
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
check_list(check_string)('my_list', ['a', 'b', 'c'])
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
To extend this concept, it's simply a matter of writing your own validator
|
|
|
|
for any particular type of object.
|
2020-06-21 02:36:20 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
"""
|
2019-01-14 07:28:04 +01:00
|
|
|
import re
|
2020-06-11 00:54:34 +02:00
|
|
|
from datetime import datetime
|
2021-07-29 17:32:18 +02:00
|
|
|
from decimal import Decimal
|
2021-04-30 00:15:33 +02:00
|
|
|
from typing import (
|
|
|
|
Any,
|
|
|
|
Callable,
|
|
|
|
Collection,
|
|
|
|
Dict,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Set,
|
|
|
|
Tuple,
|
2021-05-11 19:04:02 +02:00
|
|
|
TypeVar,
|
2021-04-30 00:15:33 +02:00
|
|
|
Union,
|
|
|
|
cast,
|
|
|
|
overload,
|
|
|
|
)
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2021-07-29 18:44:18 +02:00
|
|
|
import pytz
|
2017-04-10 08:06:10 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.core.validators import URLValidator, validate_email
|
2021-04-16 00:57:30 +02:00
|
|
|
from django.utils.translation import gettext as _
|
2017-04-10 08:06:10 +02:00
|
|
|
|
2021-07-08 16:10:54 +02:00
|
|
|
from zerver.lib.exceptions import JsonableError
|
2022-02-10 01:45:44 +01:00
|
|
|
from zerver.lib.timezone import canonicalize_timezone
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.types import ProfileFieldData, Validator
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2021-07-08 16:10:54 +02:00
|
|
|
ResultT = TypeVar("ResultT")
|
|
|
|
|
2019-12-11 12:03:20 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_string(var_name: str, val: object) -> str:
|
2017-09-27 10:06:17 +02:00
|
|
|
if not isinstance(val, str):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a string").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_required_string(var_name: str, val: object) -> str:
|
|
|
|
s = check_string(var_name, val)
|
|
|
|
if not s.strip():
|
|
|
|
raise ValidationError(_("{item} cannot be blank.").format(item=var_name))
|
|
|
|
return s
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_string_in(possible_values: Union[Set[str], List[str]]) -> Validator[str]:
|
|
|
|
def validator(var_name: str, val: object) -> str:
|
|
|
|
s = check_string(var_name, val)
|
|
|
|
if s not in possible_values:
|
|
|
|
raise ValidationError(_("Invalid {var_name}").format(var_name=var_name))
|
|
|
|
return s
|
2020-03-24 20:36:45 +01:00
|
|
|
|
|
|
|
return validator
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_short_string(var_name: str, val: object) -> str:
|
2018-04-03 14:54:35 +02:00
|
|
|
return check_capped_string(50)(var_name, val)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_capped_string(max_length: int) -> Validator[str]:
|
|
|
|
def validator(var_name: str, val: object) -> str:
|
|
|
|
s = check_string(var_name, val)
|
|
|
|
if len(s) > max_length:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
|
|
|
_("{var_name} is too long (limit: {max_length} characters)").format(
|
|
|
|
var_name=var_name,
|
|
|
|
max_length=max_length,
|
|
|
|
)
|
|
|
|
)
|
2020-06-21 02:36:20 +02:00
|
|
|
return s
|
2019-12-11 12:03:20 +01:00
|
|
|
|
2018-04-03 14:54:35 +02:00
|
|
|
return validator
|
2018-04-02 15:16:21 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_string_fixed_length(length: int) -> Validator[str]:
|
2020-06-22 22:37:00 +02:00
|
|
|
def validator(var_name: str, val: object) -> str:
|
2020-06-21 02:36:20 +02:00
|
|
|
s = check_string(var_name, val)
|
|
|
|
if len(s) != length:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
|
|
|
_("{var_name} has incorrect length {length}; should be {target_length}").format(
|
|
|
|
var_name=var_name,
|
|
|
|
target_length=length,
|
|
|
|
length=len(s),
|
|
|
|
)
|
|
|
|
)
|
2020-06-21 02:36:20 +02:00
|
|
|
return s
|
|
|
|
|
2018-05-03 23:22:05 +02:00
|
|
|
return validator
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_long_string(var_name: str, val: object) -> str:
|
2018-04-03 14:54:35 +02:00
|
|
|
return check_capped_string(500)(var_name, val)
|
2018-04-02 15:16:21 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_date(var_name: str, val: object) -> str:
|
2018-04-03 18:06:13 +02:00
|
|
|
if not isinstance(val, str):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a string").format(var_name=var_name))
|
2018-04-03 18:06:13 +02:00
|
|
|
try:
|
2021-02-12 08:20:45 +01:00
|
|
|
if datetime.strptime(val, "%Y-%m-%d").strftime("%Y-%m-%d") != val:
|
|
|
|
raise ValidationError(_("{var_name} is not a date").format(var_name=var_name))
|
2018-04-03 18:06:13 +02:00
|
|
|
except ValueError:
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a date").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2018-04-03 18:06:13 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_int(var_name: str, val: object) -> int:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, int):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not an integer").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_int_in(possible_values: List[int]) -> Validator[int]:
|
|
|
|
def validator(var_name: str, val: object) -> int:
|
|
|
|
n = check_int(var_name, val)
|
|
|
|
if n not in possible_values:
|
|
|
|
raise ValidationError(_("Invalid {var_name}").format(var_name=var_name))
|
|
|
|
return n
|
2019-11-16 17:38:27 +01:00
|
|
|
|
|
|
|
return validator
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-06-28 19:56:23 +02:00
|
|
|
def check_int_range(low: int, high: int) -> Validator[int]:
|
|
|
|
# low and high are both treated as valid values
|
|
|
|
def validator(var_name: str, val: object) -> int:
|
|
|
|
n = check_int(var_name, val)
|
|
|
|
if n < low:
|
|
|
|
raise ValidationError(_("{var_name} is too small").format(var_name=var_name))
|
|
|
|
if n > high:
|
|
|
|
raise ValidationError(_("{var_name} is too large").format(var_name=var_name))
|
|
|
|
return n
|
|
|
|
|
|
|
|
return validator
|
|
|
|
|
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_float(var_name: str, val: object) -> float:
|
2017-03-24 05:23:41 +01:00
|
|
|
if not isinstance(val, float):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a float").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2017-03-24 05:23:41 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_bool(var_name: str, val: object) -> bool:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, bool):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a boolean").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_color(var_name: str, val: object) -> str:
|
|
|
|
s = check_string(var_name, val)
|
2021-02-12 08:20:45 +01:00
|
|
|
valid_color_pattern = re.compile(r"^#([a-fA-F0-9]{3,6})$")
|
2020-06-21 02:36:20 +02:00
|
|
|
matched_results = valid_color_pattern.match(s)
|
2019-01-14 07:28:04 +01:00
|
|
|
if not matched_results:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
2021-02-12 08:20:45 +01:00
|
|
|
_("{var_name} is not a valid hex color code").format(var_name=var_name)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2020-06-21 02:36:20 +02:00
|
|
|
return s
|
2019-01-14 07:28:04 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-22 22:37:00 +02:00
|
|
|
def check_none_or(sub_validator: Validator[ResultT]) -> Validator[Optional[ResultT]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
def f(var_name: str, val: object) -> Optional[ResultT]:
|
2014-02-26 00:12:14 +01:00
|
|
|
if val is None:
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2014-02-26 00:12:14 +01:00
|
|
|
else:
|
|
|
|
return sub_validator(var_name, val)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2014-02-26 00:12:14 +01:00
|
|
|
return f
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def check_list(
|
|
|
|
sub_validator: Validator[ResultT], length: Optional[int] = None
|
|
|
|
) -> Validator[List[ResultT]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
def f(var_name: str, val: object) -> List[ResultT]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, list):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a list").format(var_name=var_name))
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2013-12-18 17:59:02 +01:00
|
|
|
if length is not None and length != len(val):
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
2021-02-12 08:20:45 +01:00
|
|
|
_("{container} should have exactly {length} items").format(
|
2021-02-12 08:19:30 +01:00
|
|
|
container=var_name,
|
|
|
|
length=length,
|
|
|
|
)
|
|
|
|
)
|
2013-12-18 18:47:32 +01:00
|
|
|
|
2020-06-24 18:49:10 +02:00
|
|
|
for i, item in enumerate(val):
|
2021-02-12 08:20:45 +01:00
|
|
|
vname = f"{var_name}[{i}]"
|
2020-06-24 18:49:10 +02:00
|
|
|
valid_item = sub_validator(vname, item)
|
|
|
|
assert item is valid_item # To justify the unchecked cast below
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
return cast(List[ResultT], val)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2013-12-12 23:47:12 +01:00
|
|
|
return f
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 19:51:35 +02:00
|
|
|
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
|
2020-06-21 02:36:20 +02:00
|
|
|
@overload
|
2021-02-12 08:19:30 +01:00
|
|
|
def check_dict(
|
2021-04-30 00:15:33 +02:00
|
|
|
required_keys: Collection[Tuple[str, Validator[object]]] = [],
|
|
|
|
optional_keys: Collection[Tuple[str, Validator[object]]] = [],
|
2021-02-12 08:19:30 +01:00
|
|
|
*,
|
|
|
|
_allow_only_listed_keys: bool = False,
|
|
|
|
) -> Validator[Dict[str, object]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
...
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
@overload
|
2021-02-12 08:19:30 +01:00
|
|
|
def check_dict(
|
2021-04-30 00:15:33 +02:00
|
|
|
required_keys: Collection[Tuple[str, Validator[ResultT]]] = [],
|
|
|
|
optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [],
|
2021-02-12 08:19:30 +01:00
|
|
|
*,
|
|
|
|
value_validator: Validator[ResultT],
|
|
|
|
_allow_only_listed_keys: bool = False,
|
|
|
|
) -> Validator[Dict[str, ResultT]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
...
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
def check_dict(
|
2021-04-30 00:15:33 +02:00
|
|
|
required_keys: Collection[Tuple[str, Validator[ResultT]]] = [],
|
|
|
|
optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [],
|
2021-02-12 08:19:30 +01:00
|
|
|
*,
|
|
|
|
value_validator: Optional[Validator[ResultT]] = None,
|
|
|
|
_allow_only_listed_keys: bool = False,
|
|
|
|
) -> Validator[Dict[str, ResultT]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
def f(var_name: str, val: object) -> Dict[str, ResultT]:
|
2013-12-12 23:47:12 +01:00
|
|
|
if not isinstance(val, dict):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a dict").format(var_name=var_name))
|
2020-06-21 02:36:20 +02:00
|
|
|
|
|
|
|
for k in val:
|
2021-02-12 08:20:45 +01:00
|
|
|
check_string(f"{var_name} key", k)
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
for k, sub_validator in required_keys:
|
|
|
|
if k not in val:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
2021-02-12 08:20:45 +01:00
|
|
|
_("{key_name} key is missing from {var_name}").format(
|
2021-02-12 08:19:30 +01:00
|
|
|
key_name=k,
|
|
|
|
var_name=var_name,
|
|
|
|
)
|
|
|
|
)
|
2020-06-10 06:41:04 +02:00
|
|
|
vname = f'{var_name}["{k}"]'
|
2020-06-21 02:36:20 +02:00
|
|
|
sub_validator(vname, val[k])
|
2013-12-12 23:47:12 +01:00
|
|
|
|
2019-01-22 19:03:21 +01:00
|
|
|
for k, sub_validator in optional_keys:
|
|
|
|
if k in val:
|
2020-06-10 06:41:04 +02:00
|
|
|
vname = f'{var_name}["{k}"]'
|
2020-06-21 02:36:20 +02:00
|
|
|
sub_validator(vname, val[k])
|
2019-01-22 19:03:21 +01:00
|
|
|
|
2018-01-07 18:55:39 +01:00
|
|
|
if value_validator:
|
|
|
|
for key in val:
|
2021-02-12 08:20:45 +01:00
|
|
|
vname = f"{var_name} contains a value that"
|
2020-06-21 02:36:20 +02:00
|
|
|
valid_value = value_validator(vname, val[key])
|
|
|
|
assert val[key] is valid_value # To justify the unchecked cast below
|
2018-01-07 18:55:39 +01:00
|
|
|
|
2017-03-26 08:11:45 +02:00
|
|
|
if _allow_only_listed_keys:
|
2020-04-09 21:51:58 +02:00
|
|
|
required_keys_set = {x[0] for x in required_keys}
|
|
|
|
optional_keys_set = {x[0] for x in optional_keys}
|
2019-01-22 19:03:21 +01:00
|
|
|
delta_keys = set(val.keys()) - required_keys_set - optional_keys_set
|
2017-03-26 08:11:45 +02:00
|
|
|
if len(delta_keys) != 0:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
|
|
|
_("Unexpected arguments: {}").format(", ".join(list(delta_keys)))
|
|
|
|
)
|
2017-03-26 08:11:45 +02:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
return cast(Dict[str, ResultT], val)
|
2013-12-12 23:47:12 +01:00
|
|
|
|
|
|
|
return f
|
2014-02-04 20:52:02 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def check_dict_only(
|
2021-04-30 00:15:33 +02:00
|
|
|
required_keys: Collection[Tuple[str, Validator[ResultT]]],
|
|
|
|
optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [],
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> Validator[Dict[str, ResultT]]:
|
2020-06-21 02:36:20 +02:00
|
|
|
return cast(
|
|
|
|
Validator[Dict[str, ResultT]],
|
|
|
|
check_dict(required_keys, optional_keys, _allow_only_listed_keys=True),
|
|
|
|
)
|
2017-03-26 08:11:45 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-04-30 00:15:33 +02:00
|
|
|
def check_union(allowed_type_funcs: Collection[Validator[ResultT]]) -> Validator[ResultT]:
|
2014-02-14 19:55:35 +01:00
|
|
|
"""
|
|
|
|
Use this validator if an argument is of a variable type (e.g. processing
|
|
|
|
properties that might be strings or booleans).
|
|
|
|
|
|
|
|
`allowed_type_funcs`: the check_* validator functions for the possible data
|
|
|
|
types for this variable.
|
|
|
|
"""
|
2019-12-11 12:03:20 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def enumerated_type_check(var_name: str, val: object) -> ResultT:
|
2014-02-14 19:55:35 +01:00
|
|
|
for func in allowed_type_funcs:
|
2020-06-21 02:36:20 +02:00
|
|
|
try:
|
|
|
|
return func(var_name, val)
|
|
|
|
except ValidationError:
|
|
|
|
pass
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not an allowed_type").format(var_name=var_name))
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2014-02-14 19:55:35 +01:00
|
|
|
return enumerated_type_check
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def equals(expected_val: ResultT) -> Validator[ResultT]:
|
|
|
|
def f(var_name: str, val: object) -> ResultT:
|
2014-02-04 20:52:02 +01:00
|
|
|
if val != expected_val:
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
2021-02-12 08:20:45 +01:00
|
|
|
_("{variable} != {expected_value} ({value} is wrong)").format(
|
2021-02-12 08:19:30 +01:00
|
|
|
variable=var_name,
|
|
|
|
expected_value=expected_val,
|
|
|
|
value=val,
|
|
|
|
)
|
|
|
|
)
|
2020-06-21 02:36:20 +02:00
|
|
|
return cast(ResultT, val)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2014-02-04 20:52:02 +01:00
|
|
|
return f
|
2017-04-10 08:06:10 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-05-11 01:40:23 +02:00
|
|
|
def validate_login_email(email: str) -> None:
|
2017-04-10 08:06:10 +02:00
|
|
|
try:
|
|
|
|
validate_email(email)
|
|
|
|
except ValidationError as err:
|
|
|
|
raise JsonableError(str(err.message))
|
2017-06-11 09:11:59 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_url(var_name: str, val: object) -> str:
|
2018-03-16 06:22:14 +01:00
|
|
|
# First, ensure val is a string
|
2020-06-21 02:36:20 +02:00
|
|
|
s = check_string(var_name, val)
|
2018-03-16 06:22:14 +01:00
|
|
|
# Now, validate as URL
|
2017-06-11 09:11:59 +02:00
|
|
|
validate = URLValidator()
|
|
|
|
try:
|
2020-06-21 02:36:20 +02:00
|
|
|
validate(s)
|
|
|
|
return s
|
2018-05-24 16:41:34 +02:00
|
|
|
except ValidationError:
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a URL").format(var_name=var_name))
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_external_account_url_pattern(var_name: str, val: object) -> str:
|
|
|
|
s = check_string(var_name, val)
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if s.count("%(username)s") != 1:
|
|
|
|
raise ValidationError(_("Malformed URL pattern."))
|
|
|
|
url_val = s.replace("%(username)s", "username")
|
2020-06-21 02:36:20 +02:00
|
|
|
|
|
|
|
check_url(var_name, url_val)
|
|
|
|
return s
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-03-24 12:48:00 +01:00
|
|
|
def validate_select_field_data(field_data: ProfileFieldData) -> Dict[str, Dict[str, str]]:
|
2018-04-08 09:50:05 +02:00
|
|
|
"""
|
|
|
|
This function is used to validate the data sent to the server while
|
|
|
|
creating/editing choices of the choice field in Organization settings.
|
|
|
|
"""
|
2021-02-12 08:19:30 +01:00
|
|
|
validator = check_dict_only(
|
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
("text", check_required_string),
|
|
|
|
("order", check_required_string),
|
2021-02-12 08:19:30 +01:00
|
|
|
]
|
|
|
|
)
|
2018-04-08 09:50:05 +02:00
|
|
|
|
|
|
|
for key, value in field_data.items():
|
|
|
|
if not key.strip():
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("'{item}' cannot be blank.").format(item="value"))
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
valid_value = validator("field_data", value)
|
2020-06-21 02:36:20 +02:00
|
|
|
assert value is valid_value # To justify the unchecked cast below
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
return cast(Dict[str, Dict[str, str]], field_data)
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-03-24 12:48:00 +01:00
|
|
|
def validate_select_field(var_name: str, field_data: str, value: object) -> str:
|
2018-04-08 09:50:05 +02:00
|
|
|
"""
|
|
|
|
This function is used to validate the value selected by the user against a
|
|
|
|
choice field. This is not used to validate admin data.
|
|
|
|
"""
|
2020-06-21 02:36:20 +02:00
|
|
|
s = check_string(var_name, value)
|
2020-08-07 01:09:47 +02:00
|
|
|
field_data_dict = orjson.loads(field_data)
|
2020-06-21 02:36:20 +02:00
|
|
|
if s not in field_data_dict:
|
2018-04-08 09:50:05 +02:00
|
|
|
msg = _("'{value}' is not a valid choice for '{field_name}'.")
|
2020-06-21 02:36:20 +02:00
|
|
|
raise ValidationError(msg.format(value=value, field_name=var_name))
|
|
|
|
return s
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_widget_content(widget_content: object) -> Dict[str, Any]:
|
2018-05-21 15:23:46 +02:00
|
|
|
if not isinstance(widget_content, dict):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError("widget_content is not a dict")
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if "widget_type" not in widget_content:
|
|
|
|
raise ValidationError("widget_type is not in widget_content")
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if "extra_data" not in widget_content:
|
|
|
|
raise ValidationError("extra_data is not in widget_content")
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
widget_type = widget_content["widget_type"]
|
|
|
|
extra_data = widget_content["extra_data"]
|
2018-05-21 15:23:46 +02:00
|
|
|
|
|
|
|
if not isinstance(extra_data, dict):
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError("extra_data is not a dict")
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if widget_type == "zform":
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if "type" not in extra_data:
|
|
|
|
raise ValidationError("zform is missing type field")
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if extra_data["type"] == "choices":
|
2018-05-21 15:23:46 +02:00
|
|
|
check_choices = check_list(
|
2021-02-12 08:19:30 +01:00
|
|
|
check_dict(
|
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
("short_name", check_string),
|
|
|
|
("long_name", check_string),
|
|
|
|
("reply", check_string),
|
2021-02-12 08:19:30 +01:00
|
|
|
]
|
|
|
|
),
|
2018-05-21 15:23:46 +02:00
|
|
|
)
|
|
|
|
|
2020-06-25 01:28:06 +02:00
|
|
|
# We re-check "type" here just to avoid it looking
|
|
|
|
# like we have extraneous keys.
|
2021-02-12 08:19:30 +01:00
|
|
|
checker = check_dict(
|
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
("type", equals("choices")),
|
|
|
|
("heading", check_string),
|
|
|
|
("choices", check_choices),
|
2021-02-12 08:19:30 +01:00
|
|
|
]
|
|
|
|
)
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
checker("extra_data", extra_data)
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
return widget_content
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError("unknown zform type: " + extra_data["type"])
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError("unknown widget type: " + widget_type)
|
2019-02-09 23:57:54 +01:00
|
|
|
|
|
|
|
|
2021-06-28 19:56:23 +02:00
|
|
|
# This should match MAX_IDX in our client widgets. It is somewhat arbitrary.
|
|
|
|
MAX_IDX = 1000
|
|
|
|
|
|
|
|
|
2021-06-13 17:00:45 +02:00
|
|
|
def validate_poll_data(poll_data: object, is_widget_author: bool) -> None:
|
|
|
|
check_dict([("type", check_string)])("poll data", poll_data)
|
|
|
|
|
|
|
|
assert isinstance(poll_data, dict)
|
|
|
|
|
|
|
|
if poll_data["type"] == "vote":
|
|
|
|
checker = check_dict_only(
|
|
|
|
[
|
|
|
|
("type", check_string),
|
|
|
|
("key", check_string),
|
|
|
|
("vote", check_int_in([1, -1])),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
checker("poll data", poll_data)
|
|
|
|
return
|
|
|
|
|
|
|
|
if poll_data["type"] == "question":
|
|
|
|
if not is_widget_author:
|
|
|
|
raise ValidationError("You can't edit a question unless you are the author.")
|
|
|
|
|
|
|
|
checker = check_dict_only(
|
|
|
|
[
|
|
|
|
("type", check_string),
|
|
|
|
("question", check_string),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
checker("poll data", poll_data)
|
|
|
|
return
|
|
|
|
|
|
|
|
if poll_data["type"] == "new_option":
|
|
|
|
checker = check_dict_only(
|
|
|
|
[
|
|
|
|
("type", check_string),
|
|
|
|
("option", check_string),
|
2021-06-28 19:56:23 +02:00
|
|
|
("idx", check_int_range(0, MAX_IDX)),
|
2021-06-13 17:00:45 +02:00
|
|
|
]
|
|
|
|
)
|
|
|
|
checker("poll data", poll_data)
|
|
|
|
return
|
|
|
|
|
|
|
|
raise ValidationError(f"Unknown type for poll data: {poll_data['type']}")
|
|
|
|
|
|
|
|
|
2021-06-28 18:55:42 +02:00
|
|
|
def validate_todo_data(todo_data: object) -> None:
|
|
|
|
check_dict([("type", check_string)])("todo data", todo_data)
|
|
|
|
|
|
|
|
assert isinstance(todo_data, dict)
|
|
|
|
|
|
|
|
if todo_data["type"] == "new_task":
|
|
|
|
checker = check_dict_only(
|
|
|
|
[
|
|
|
|
("type", check_string),
|
2021-06-28 19:56:23 +02:00
|
|
|
("key", check_int_range(0, MAX_IDX)),
|
2021-06-28 18:55:42 +02:00
|
|
|
("task", check_string),
|
|
|
|
("desc", check_string),
|
|
|
|
("completed", check_bool),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
checker("todo data", todo_data)
|
|
|
|
return
|
|
|
|
|
|
|
|
if todo_data["type"] == "strike":
|
|
|
|
checker = check_dict_only(
|
|
|
|
[
|
|
|
|
("type", check_string),
|
|
|
|
("key", check_string),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
checker("todo data", todo_data)
|
|
|
|
return
|
|
|
|
|
|
|
|
raise ValidationError(f"Unknown type for todo data: {todo_data['type']}")
|
|
|
|
|
|
|
|
|
2019-02-09 23:57:54 +01:00
|
|
|
# Converter functions for use with has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def to_non_negative_int(s: str, max_int_size: int = 2 ** 32 - 1) -> int:
|
2019-02-09 23:57:54 +01:00
|
|
|
x = int(s)
|
|
|
|
if x < 0:
|
|
|
|
raise ValueError("argument is negative")
|
|
|
|
if x > max_int_size:
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValueError(f"{x} is too large (max {max_int_size})")
|
2019-02-09 23:57:54 +01:00
|
|
|
return x
|
2019-06-08 23:19:56 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-07-29 18:08:59 +02:00
|
|
|
def to_float(s: str) -> float:
|
|
|
|
return float(s)
|
|
|
|
|
|
|
|
|
2021-07-29 17:32:18 +02:00
|
|
|
def to_decimal(s: str) -> Decimal:
|
|
|
|
return Decimal(s)
|
|
|
|
|
|
|
|
|
2021-07-29 18:44:18 +02:00
|
|
|
def to_timezone_or_empty(s: str) -> str:
|
|
|
|
if s in pytz.all_timezones_set:
|
2022-02-10 01:45:44 +01:00
|
|
|
return canonicalize_timezone(s)
|
2021-07-29 18:44:18 +02:00
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
def to_converted_or_fallback(
|
|
|
|
sub_converter: Callable[[str], ResultT], default: ResultT
|
|
|
|
) -> Callable[[str], ResultT]:
|
|
|
|
def converter(s: str) -> ResultT:
|
|
|
|
try:
|
|
|
|
return sub_converter(s)
|
|
|
|
except ValueError:
|
|
|
|
return default
|
|
|
|
|
|
|
|
return converter
|
|
|
|
|
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_string_or_int_list(var_name: str, val: object) -> Union[str, List[int]]:
|
2019-06-08 23:19:56 +02:00
|
|
|
if isinstance(val, str):
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2019-06-08 23:19:56 +02:00
|
|
|
|
|
|
|
if not isinstance(val, list):
|
2021-02-12 08:19:30 +01:00
|
|
|
raise ValidationError(
|
2021-02-12 08:20:45 +01:00
|
|
|
_("{var_name} is not a string or an integer list").format(var_name=var_name)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-06-08 23:19:56 +02:00
|
|
|
|
|
|
|
return check_list(check_int)(var_name, val)
|
2019-07-13 01:48:04 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
def check_string_or_int(var_name: str, val: object) -> Union[str, int]:
|
2020-06-24 01:37:14 +02:00
|
|
|
if isinstance(val, (str, int)):
|
2020-06-21 02:36:20 +02:00
|
|
|
return val
|
2019-07-13 01:48:04 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
raise ValidationError(_("{var_name} is not a string or integer").format(var_name=var_name))
|