Coverage for functions \ flipdare \ request \ request_validator.py: 93%

72 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13 

14from abc import ABC, abstractmethod 

15from typing import override 

16from flipdare.app_globals import is_text_present, is_valid_doc_id, is_valid_email 

17from flipdare.app_log import LOG 

18from flipdare.app_types import JsonDict 

19from flipdare.constants import HONEYPOT_NAME, IS_DEBUG 

20from flipdare.core.ip_address_dto import IpAddressDTO 

21 

22__all__ = [ 

23 "RequestValidator", 

24 "EmailRequestValidator", 

25 "HoneyPotRequestValidator", 

26 "UidRequestValidator", 

27 "PaymentCustomerUidRequestValidator", 

28 "PaymentAccountUidRequestValidator", 

29 "TextPresentRequestValidator", 

30 "IpAddressRequestValidator", 

31] 

32 

33# NOTE: These are field validators ONLY. 

34# NOTE: DONT use FOR AUTHENTICATION 

35 

36 

37class RequestValidator(ABC): 

38 def __init__(self, data: JsonDict) -> None: 

39 self._data = data 

40 

41 @abstractmethod 

42 def validate(self) -> list[str]: ... 

43 

44 

45class IpAddressRequestValidator(RequestValidator): 

46 @override 

47 def validate(self) -> list[str]: 

48 ip_address = self._data.get("ip_address") 

49 if IS_DEBUG: 

50 LOG().debug(f"Validating IP address: {ip_address}") 

51 if ip_address is None: 

52 return ["Missing required field: IP address"] 

53 try: 

54 IpAddressDTO.create(ip_address) 

55 return [] 

56 except ValueError: 

57 return [f"Invalid IP address format: {ip_address}"] 

58 

59 

60class EmailRequestValidator(RequestValidator): 

61 @override 

62 def validate(self) -> list[str]: 

63 email = self._data.get("email") 

64 if email is None: 

65 return ["Missing required field: email"] 

66 if IS_DEBUG: 

67 LOG().debug(f"Validating email: {email}") 

68 # note: dont check availability here, just format. 

69 result = is_valid_email(email) 

70 if result.error is not None: 

71 return [f"Invalid email: {email}"] 

72 return [] 

73 

74 

75class HoneyPotRequestValidator(RequestValidator): 

76 # If honeypot_field is NOT empty: reject (possible bot submission). 

77 @override 

78 def validate(self) -> list[str]: 

79 if IS_DEBUG: 

80 LOG().debug("Checking honeypot field") 

81 honeypot = self._data.get(HONEYPOT_NAME) 

82 if honeypot is not None: 

83 return ["Honeypot field should be empty, possible bot submission."] 

84 return [] 

85 

86 

87class UidRequestValidator(RequestValidator): 

88 @override 

89 def validate(self) -> list[str]: 

90 return self._validate_key("uid") 

91 

92 def _validate_key(self, key: str) -> list[str]: 

93 if IS_DEBUG: 

94 LOG().debug(f"Checking {key} field") 

95 value = self._data.get(key) 

96 if value is None: 

97 return [f"Missing {key} field in request."] 

98 if not is_valid_doc_id(value): 

99 return [f"Invalid {key} format: {value}"] 

100 return [] 

101 

102 

103class PaymentCustomerUidRequestValidator(UidRequestValidator): 

104 @override 

105 def validate(self) -> list[str]: 

106 return self._validate_key("customer_uid") 

107 

108 

109class PaymentAccountUidRequestValidator(UidRequestValidator): 

110 @override 

111 def validate(self) -> list[str]: 

112 return self._validate_key("account_uid") 

113 

114 

115class TextPresentRequestValidator(RequestValidator): 

116 @override 

117 def validate(self) -> list[str]: 

118 if IS_DEBUG: 

119 LOG().debug("Checking text presence") 

120 return [ 

121 f"Missing or empty required field: {field_name}" 

122 for field_name, value in self._data.items() 

123 if isinstance(value, str) and not is_text_present(value) 

124 ]