File size: 9,178 Bytes
aef804e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Authentication endpoint fuzzing harnesses.

This module uses Atheris to discover crashes in authentication endpoint
parsing and validation code through coverage-guided fuzzing.

Fuzzing Targets:
- POST /api/auth/login - Login endpoint
- POST /api/auth/signup - Signup endpoint
- POST /api/auth/mobile/login - Mobile login endpoint

Usage:
    FUZZ_ITERATIONS=10000 pytest tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
"""

import os
import sys
import json
import pytest
from typing import Dict, Any

# Add backend to path
backend_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if backend_dir not in sys.path:
    sys.path.insert(0, backend_dir)

from fastapi.testclient import TestClient
from sqlalchemy.orm import Session

# Import fixtures and app
from tests.fuzzing.conftest import ATHERIS_AVAILABLE
from tests.e2e_ui.fixtures.database_fixtures import db_session
from tests.e2e_ui.fixtures.auth_fixtures import authenticated_user, test_user

from main_api_app import app
from core.database import get_db

# Try to import Atheris (graceful degradation)
try:
    import atheris
    from atheris import fp
except ImportError:
    ATHERIS_AVAILABLE = False
    pytest.skip("Atheris not installed - skipping auth endpoint fuzzing", allow_module_level=True)


# ============================================================================
# FUZZING TEST 1: LOGIN ENDPOINT
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_login_endpoint_fuzzing(db_session: Session):
    """
    Fuzz POST /api/auth/login endpoint.

    PROPERTY: Login endpoint should not crash on malformed input.
    STRATEGY: FuzzedDataProvider generates random email/password combinations.
    INVARIANT: Response status code in [200, 400, 401, 422] (no 500 errors).
    RADII: 10000 iterations sufficient for login input space (email + password).

    Fuzzed fields:
    - email: Random string up to 100 chars
    - password: Random string up to 100 chars
    """
    # Override database dependency for test isolation
    app.dependency_overrides[get_db] = lambda: db_session

    # Create TestClient for fuzzing
    client = TestClient(app)

    def fuzz_one_input(data: bytes):
        """Fuzzing target for login endpoint."""
        try:
            # Generate fuzzed payload using FuzzedDataProvider
            fdp = fp.FuzzedDataProvider(data)

            # Consume random strings for email and password
            email = fdp.ConsumeRandomLengthString(100)
            password = fdp.ConsumeRandomLengthString(100)

            # Call login endpoint with fuzzed data
            response = client.post(
                "/api/auth/login",
                json={"email": email, "password": password}
            )

            # Assert no crashes (status in expected range)
            assert response.status_code in [200, 400, 401, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except (ValueError, json.decoder.JSONDecodeError) as e:
            # Expected errors (malformed JSON)
            pass
        except Exception as e:
            # Potential bug - re-raise for Atheris to capture
            raise Exception(f"Login endpoint crashed: {e}")

    # Setup Atheris with libFuzzer
    atheris.Setup(
        sys.argv + [atheris.FuzzedDataProviderFlag],
        fuzz_one_input
    )

    # Run fuzzing (default 10000 iterations or FUZZ_ITERATIONS env var)
    iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
    atheris.Fuzz(iterations=iterations)

    # Clean up dependency override
    app.dependency_overrides = {}


# ============================================================================
# FUZZING TEST 2: SIGNUP ENDPOINT
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_signup_endpoint_fuzzing(db_session: Session):
    """
    Fuzz POST /api/auth/signup endpoint.

    PROPERTY: Signup endpoint should not crash on malformed input.
    STRATEGY: FuzzedDataProvider generates random signup payloads.
    INVARIANT: Response status code in [200, 400, 409, 422] (no 500 errors).
    RADII: 10000 iterations sufficient for signup input space (email + password + confirm + name).

    Fuzzed fields:
    - email: Random string up to 100 chars
    - password: Random string up to 100 chars
    - confirm_password: Random string up to 100 chars
    - name: Random string up to 100 chars
    """
    import json

    # Override database dependency
    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)

    def fuzz_one_input(data: bytes):
        """Fuzzing target for signup endpoint."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Consume random strings for signup fields
            email = fdp.ConsumeRandomLengthString(100)
            password = fdp.ConsumeRandomLengthString(100)
            confirm_password = fdp.ConsumeRandomLengthString(100)
            name = fdp.ConsumeRandomLengthString(100)

            # Call signup endpoint with fuzzed data
            response = client.post(
                "/api/auth/signup",
                json={
                    "email": email,
                    "password": password,
                    "confirm_password": confirm_password,
                    "name": name
                }
            )

            # Assert no crashes
            assert response.status_code in [200, 400, 409, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except (ValueError, json.decoder.JSONDecodeError):
            pass
        except Exception as e:
            raise Exception(f"Signup endpoint crashed: {e}")

    atheris.Setup(
        sys.argv + [atheris.FuzzedDataProviderFlag],
        fuzz_one_input
    )

    iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
    atheris.Fuzz(iterations=iterations)

    app.dependency_overrides = {}


# ============================================================================
# FUZZING TEST 3: MOBILE LOGIN ENDPOINT
# ============================================================================

@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_mobile_login_fuzzing(db_session: Session, authenticated_user):
    """
    Fuzz POST /api/auth/mobile/login endpoint.

    PROPERTY: Mobile login endpoint should not crash on malformed device tokens.
    STRATEGY: FuzzedDataProvider generates random device tokens and platforms.
    INVARIANT: Response status code in [200, 400, 401, 422] (no 500 errors).
    RADII: 10000 iterations sufficient for device token/platform space.

    Fuzzed fields:
    - device_token: Random string up to 100 chars
    - platform: Random string up to 20 chars (ios, android, or random)
    - device_info: Random dictionary with nested fuzzed data

    Uses authenticated_user fixture for valid credentials baseline.
    """
    import json

    # Override database dependency
    app.dependency_overrides[get_db] = lambda: db_session
    client = TestClient(app)

    # Get valid credentials from authenticated_user
    user, _ = authenticated_user
    valid_email = user.email

    def fuzz_one_input(data: bytes):
        """Fuzzing target for mobile login endpoint."""
        try:
            fdp = fp.FuzzedDataProvider(data)

            # Consume random strings for device fields
            device_token = fdp.ConsumeRandomLengthString(100)
            platform = fdp.ConsumeRandomLengthString(20)

            # Generate fuzzed device_info dict
            device_info: Dict[str, Any] = {}
            if fdp.ConsumeBool():
                device_info["device_id"] = fdp.ConsumeRandomLengthString(50)
            if fdp.ConsumeBool():
                device_info["app_version"] = fdp.ConsumeRandomLengthString(20)
            if fdp.ConsumeBool():
                device_info["os_version"] = fdp.ConsumeRandomLengthString(20)

            # Call mobile login endpoint with fuzzed device data
            response = client.post(
                "/api/auth/mobile/login",
                json={
                    "email": valid_email,
                    "password": "TestPassword123!",  # Valid password from test_user fixture
                    "device_token": device_token,
                    "platform": platform,
                    "device_info": device_info
                }
            )

            # Assert no crashes
            assert response.status_code in [200, 400, 401, 422], \
                f"Unexpected status {response.status_code}: {response.text[:200]}"

        except (ValueError, json.decoder.JSONDecodeError):
            pass
        except Exception as e:
            raise Exception(f"Mobile login endpoint crashed: {e}")

    atheris.Setup(
        sys.argv + [atheris.FuzzedDataProviderFlag],
        fuzz_one_input
    )

    iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
    atheris.Fuzz(iterations=iterations)

    app.dependency_overrides = {}