-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinput_validator.py
More file actions
312 lines (226 loc) · 8.33 KB
/
input_validator.py
File metadata and controls
312 lines (226 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
"""
Input Validator Module
Provides comprehensive input validation to prevent SQL injection and ensure data integrity.
"""
import re
import html
from typing import Tuple, Optional
# Suspicious SQL patterns that might indicate injection attempts
SQL_INJECTION_PATTERNS = [
r"(\b(SELECT|INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE|EXEC|EXECUTE|UNION|GRANT|REVOKE)\b)",
r"(-{2,})", # SQL comments
r"(;)", # Statement terminator
r"(/\*|\*/)", # Block comments
r"(\bOR\b.*=.*)", # OR 1=1 style attacks
r"(\bAND\b.*=.*)", # AND 1=1 style attacks
r"('.*--)", # Quote followed by comment
r"(\bxp_\w+)", # Extended stored procedures
r"(\bsp_\w+)", # Stored procedures
]
# Compile patterns for performance
COMPILED_PATTERNS = [re.compile(pattern, re.IGNORECASE) for pattern in SQL_INJECTION_PATTERNS]
class ValidationError(Exception):
"""Custom exception for validation errors"""
pass
def contains_sql_injection(value: str) -> bool:
"""
Check if a string contains potential SQL injection patterns.
Args:
value: The string to check
Returns:
True if suspicious patterns are found, False otherwise
"""
if not value:
return False
for pattern in COMPILED_PATTERNS:
if pattern.search(value):
return True
return False
def validate_name(name: str) -> Tuple[bool, str]:
"""
Validate a student name.
Rules:
- Must not be empty
- Must only contain letters and spaces
- Must be between 2 and 100 characters
- Must not contain SQL injection patterns
Args:
name: The name to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not name or not name.strip():
return False, "Name cannot be empty"
name = name.strip()
if len(name) < 2:
return False, "Name must be at least 2 characters long"
if len(name) > 100:
return False, "Name cannot exceed 100 characters"
# Check for SQL injection patterns
if contains_sql_injection(name):
return False, "Invalid characters detected in name"
# Allow only letters and spaces as per strict validation rules
if not re.match(r"^[a-zA-Z\s]+$", name):
return False, "Name can only contain letters and spaces"
return True, ""
def validate_roll_number(roll_no: str) -> Tuple[bool, str, Optional[int]]:
"""
Validate a roll number.
Rules:
- Must not be empty
- Must be a positive integer
- Must be within reasonable range (1 to 999999)
Args:
roll_no: The roll number to validate (as string from input)
Returns:
Tuple of (is_valid, error_message, parsed_value)
"""
if not roll_no or not roll_no.strip():
return False, "Roll Number cannot be empty", None
roll_no = roll_no.strip()
# Check for SQL injection patterns
if contains_sql_injection(roll_no):
return False, "Invalid characters detected in Roll Number", None
try:
value = int(roll_no)
except ValueError:
return False, "Roll Number must be a valid integer", None
if value <= 0:
return False, "Roll Number must be a positive number", None
if value > 999999:
return False, "Roll Number cannot exceed 999999", None
return True, "", value
def validate_marks(marks: str, subject_name: str = "Subject") -> Tuple[bool, str, Optional[int]]:
"""
Validate marks for a subject.
Rules:
- Can be empty (optional)
- If provided, must be integer between 0 and 100
Args:
marks: The marks value to validate (as string from input)
subject_name: Name of the subject for error messages
Returns:
Tuple of (is_valid, error_message, parsed_value)
"""
if not marks or not marks.strip():
return True, "", None # Empty marks are allowed
marks = marks.strip()
# Check for SQL injection patterns
if contains_sql_injection(marks):
return False, f"Invalid characters detected in {subject_name} marks", None
try:
value = int(marks)
except ValueError:
return False, f"Marks for {subject_name} must be a valid integer", None
if value < 0:
return False, f"Marks for {subject_name} cannot be negative", None
if value > 100:
return False, f"Marks for {subject_name} cannot exceed 100", None
return True, "", value
def validate_email(email: str) -> Tuple[bool, str]:
"""
Validate an email address.
Rules:
- Must follow standard email format
Args:
email: The email string to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not email:
return False, "Email cannot be empty"
email = email.strip()
if len(email) > 254:
return False, "Email is too long"
# Check for SQL injection patterns
if contains_sql_injection(email):
return False, "Invalid characters detected in email"
# Standard email regex
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
return False, "Invalid email format"
return True, ""
def validate_message(message: str) -> Tuple[bool, str]:
"""
Validate a message/comment.
Rules:
- Alphanumeric + basic punctuation (.,!?) only
Args:
message: The message string to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not message:
return True, "" # Empty message might be allowed
# Check for SQL injection patterns
if contains_sql_injection(message):
return False, "Invalid characters detected in message"
# Alphanumeric + basic punctuation only
if not re.match(r"^[a-zA-Z0-9\s.,!?]+$", message):
return False, "Message can only contain letters, numbers, spaces, and basic punctuation (.,!?)"
return True, ""
def sanitize_string(value: str) -> str:
"""
Sanitize a string by removing potentially dangerous characters.
This is an additional layer of defense, not a replacement for parameterized queries.
Args:
value: The string to sanitize
Returns:
Sanitized string
"""
if not value:
return ""
# Remove null bytes
value = value.replace('\x00', '')
# Strip leading/trailing whitespace
value = value.strip()
# Escape HTML special characters to prevent XSS
value = html.escape(value)
return value
def validate_student_data(name: str, roll_no: str, marks_dict: dict) -> Tuple[bool, str, dict]:
"""
Validate all student data at once.
Args:
name: Student name
roll_no: Roll number as string
marks_dict: Dictionary of subject -> marks (as strings)
Returns:
Tuple of (is_valid, error_message, validated_data_dict)
"""
validated_data = {}
# Validate name
is_valid, error_msg = validate_name(name)
if not is_valid:
return False, error_msg, {}
validated_data['name'] = sanitize_string(name)
# Validate roll number
is_valid, error_msg, roll_value = validate_roll_number(roll_no)
if not is_valid:
return False, error_msg, {}
validated_data['roll_no'] = roll_value
# Validate marks
validated_marks = {}
for subject, marks in marks_dict.items():
is_valid, error_msg, marks_value = validate_marks(marks, subject)
if not is_valid:
return False, error_msg, {}
if marks_value is not None:
validated_marks[subject] = marks_value
validated_data['marks'] = validated_marks
return True, "", validated_data
def validate_search_term(search_term: str) -> Tuple[bool, str]:
"""
Validate a search term.
Args:
search_term: The search term to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not search_term:
return True, "" # Empty search is allowed
# Check for SQL injection patterns
if contains_sql_injection(search_term):
return False, "Invalid characters detected in search term"
if len(search_term) > 100:
return False, "Search term is too long"
return True, ""