aiwaf 0.1.8.5__py3-none-any.whl → 0.1.8.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiwaf might be problematic. Click here for more details.

aiwaf/decorators.py ADDED
@@ -0,0 +1,28 @@
1
+ from functools import wraps
2
+ from django.utils.decorators import method_decorator
3
+
4
+ def aiwaf_exempt(view_func):
5
+ """
6
+ Decorator to exempt a view from AI-WAF protection.
7
+ Can be used on function-based views or class-based views.
8
+
9
+ Usage:
10
+ @aiwaf_exempt
11
+ def my_view(request):
12
+ return HttpResponse("This view is exempt from AI-WAF")
13
+
14
+ # Or for class-based views:
15
+ @method_decorator(aiwaf_exempt, name='dispatch')
16
+ class MyView(View):
17
+ pass
18
+ """
19
+ @wraps(view_func)
20
+ def wrapped_view(*args, **kwargs):
21
+ return view_func(*args, **kwargs)
22
+
23
+ # Mark the view as AI-WAF exempt
24
+ wrapped_view.aiwaf_exempt = True
25
+ return wrapped_view
26
+
27
+ # For class-based views
28
+ aiwaf_exempt_view = method_decorator(aiwaf_exempt, name='dispatch')
aiwaf/middleware.py CHANGED
@@ -14,37 +14,10 @@ from django.core.cache import cache
14
14
  from django.db.models import F
15
15
  from django.apps import apps
16
16
  from django.urls import get_resolver
17
- from .trainer import STATIC_KW, STATUS_IDX, is_exempt_path, path_exists_in_django
17
+ from .trainer import STATIC_KW, STATUS_IDX, path_exists_in_django
18
18
  from .blacklist_manager import BlacklistManager
19
19
  from .models import DynamicKeyword, IPExemption
20
- def is_ip_exempted(ip):
21
- return IPExemption.objects.filter(ip_address=ip).exists()
22
-
23
- def is_exempt_path(path):
24
- path = path.lower()
25
-
26
- # Default login paths that should always be exempt
27
- default_login_paths = [
28
- "/admin/login/",
29
- "/admin/",
30
- "/login/",
31
- "/accounts/login/",
32
- "/auth/login/",
33
- "/signin/",
34
- ]
35
-
36
- # Check default login paths
37
- for login_path in default_login_paths:
38
- if path.startswith(login_path):
39
- return True
40
-
41
- # Check user-configured exempt paths
42
- exempt_paths = getattr(settings, "AIWAF_EXEMPT_PATHS", [])
43
- for exempt in exempt_paths:
44
- if path == exempt or path.startswith(exempt.rstrip("/") + "/"):
45
- return True
46
-
47
- return False
20
+ from .utils import is_exempt, get_ip, is_ip_exempted
48
21
 
49
22
  MODEL_PATH = getattr(
50
23
  settings,
@@ -93,7 +66,7 @@ class IPAndKeywordBlockMiddleware:
93
66
 
94
67
  def __call__(self, request):
95
68
  raw_path = request.path.lower()
96
- if is_exempt_path(raw_path):
69
+ if is_exempt(request):
97
70
  return self.get_response(request)
98
71
  ip = get_ip(request)
99
72
  path = raw_path.lstrip("/")
@@ -132,7 +105,7 @@ class RateLimitMiddleware:
132
105
  self.get_response = get_response
133
106
 
134
107
  def __call__(self, request):
135
- if is_exempt_path(request.path):
108
+ if is_exempt(request):
136
109
  return self.get_response(request)
137
110
 
138
111
  ip = get_ip(request)
@@ -161,7 +134,7 @@ class AIAnomalyMiddleware(MiddlewareMixin):
161
134
  self.model = joblib.load(model_path)
162
135
 
163
136
  def process_request(self, request):
164
- if is_exempt_path(request.path):
137
+ if is_exempt(request):
165
138
  return None
166
139
  request._start_time = time.time()
167
140
  ip = get_ip(request)
@@ -172,14 +145,14 @@ class AIAnomalyMiddleware(MiddlewareMixin):
172
145
  return None
173
146
 
174
147
  def process_response(self, request, response):
175
- if is_exempt_path(request.path):
148
+ if is_exempt(request):
176
149
  return response
177
150
  ip = get_ip(request)
178
151
  now = time.time()
179
152
  key = f"aiwaf:{ip}"
180
153
  data = cache.get(key, [])
181
154
  path_len = len(request.path)
182
- if not path_exists_in_django(request.path) and not is_exempt_path(request.path):
155
+ if not path_exists_in_django(request.path) and not is_exempt(request):
183
156
  kw_hits = sum(1 for kw in STATIC_KW if kw in request.path.lower())
184
157
  else:
185
158
  kw_hits = 0
@@ -211,7 +184,7 @@ class HoneypotTimingMiddleware(MiddlewareMixin):
211
184
  MIN_FORM_TIME = getattr(settings, "AIWAF_MIN_FORM_TIME", 1.0) # seconds
212
185
 
213
186
  def process_request(self, request):
214
- if is_exempt_path(request.path):
187
+ if is_exempt(request):
215
188
  return None
216
189
 
217
190
  ip = get_ip(request)
@@ -255,7 +228,7 @@ class HoneypotTimingMiddleware(MiddlewareMixin):
255
228
 
256
229
  class UUIDTamperMiddleware(MiddlewareMixin):
257
230
  def process_view(self, request, view_func, view_args, view_kwargs):
258
- if is_exempt_path(request.path):
231
+ if is_exempt(request):
259
232
  return None
260
233
  uid = view_kwargs.get("uuid")
261
234
  if not uid:
aiwaf/trainer.py CHANGED
@@ -13,6 +13,7 @@ from sklearn.ensemble import IsolationForest
13
13
  from django.conf import settings
14
14
  from django.apps import apps
15
15
  from django.db.models import F
16
+ from .utils import is_exempt_path
16
17
 
17
18
  # ─────────── Configuration ───────────
18
19
  LOG_PATH = settings.AIWAF_ACCESS_LOG
@@ -32,31 +33,6 @@ DynamicKeyword = apps.get_model("aiwaf", "DynamicKeyword")
32
33
  IPExemption = apps.get_model("aiwaf", "IPExemption")
33
34
 
34
35
 
35
- def is_exempt_path(path: str) -> bool:
36
- path = path.lower()
37
-
38
- # Default login paths that should always be exempt
39
- default_login_paths = [
40
- "/admin/login/",
41
- "/admin/",
42
- "/login/",
43
- "/accounts/login/",
44
- "/auth/login/",
45
- "/signin/",
46
- ]
47
-
48
- # Check default login paths
49
- for login_path in default_login_paths:
50
- if path.startswith(login_path):
51
- return True
52
-
53
- # Check user-configured exempt paths
54
- for exempt in getattr(settings, "AIWAF_EXEMPT_PATHS", []):
55
- if path == exempt or path.startswith(exempt.rstrip("/") + "/"):
56
- return True
57
- return False
58
-
59
-
60
36
  def path_exists_in_django(path: str) -> bool:
61
37
  from django.urls import get_resolver
62
38
  from django.urls.resolvers import URLResolver
aiwaf/utils.py CHANGED
@@ -3,6 +3,8 @@ import re
3
3
  import glob
4
4
  import gzip
5
5
  from datetime import datetime
6
+ from django.conf import settings
7
+ from .models import IPExemption
6
8
 
7
9
  _LOG_RX = re.compile(
8
10
  r'(\d+\.\d+\.\d+\.\d+).*\[(.*?)\].*"(GET|POST) (.*?) HTTP/.*?" (\d{3}).*?"(.*?)" "(.*?)"'
@@ -47,4 +49,57 @@ def parse_log_line(line):
47
49
  "referer": ref,
48
50
  "user_agent": ua,
49
51
  "response_time": rt
50
- }
52
+ }
53
+
54
+ def is_ip_exempted(ip):
55
+ """Check if IP is in exemption list"""
56
+ return IPExemption.objects.filter(ip_address=ip).exists()
57
+
58
+ def is_view_exempt(request):
59
+ """Check if the current view is marked as AI-WAF exempt"""
60
+ if hasattr(request, 'resolver_match') and request.resolver_match:
61
+ view_func = request.resolver_match.func
62
+
63
+ # Check if view function has aiwaf_exempt attribute
64
+ if hasattr(view_func, 'aiwaf_exempt'):
65
+ return True
66
+
67
+ # For class-based views, check the view class
68
+ if hasattr(view_func, 'view_class'):
69
+ view_class = view_func.view_class
70
+ if hasattr(view_class, 'aiwaf_exempt'):
71
+ return True
72
+
73
+ # Check dispatch method for method_decorator usage
74
+ dispatch_method = getattr(view_class, 'dispatch', None)
75
+ if dispatch_method and hasattr(dispatch_method, 'aiwaf_exempt'):
76
+ return True
77
+
78
+ return False
79
+
80
+ def is_exempt_path(path):
81
+ """Check if path should be exempt from AI-WAF"""
82
+ path = path.lower()
83
+
84
+ # Default login paths (always exempt)
85
+ default_exempt = [
86
+ "/admin/login/", "/admin/", "/login/", "/accounts/login/",
87
+ "/auth/login/", "/signin/"
88
+ ]
89
+
90
+ # Check default exempt paths
91
+ for exempt_path in default_exempt:
92
+ if path.startswith(exempt_path):
93
+ return True
94
+
95
+ # Check configured exempt paths
96
+ exempt_paths = getattr(settings, "AIWAF_EXEMPT_PATHS", [])
97
+ for exempt_path in exempt_paths:
98
+ if path == exempt_path or path.startswith(exempt_path.rstrip("/") + "/"):
99
+ return True
100
+
101
+ return False
102
+
103
+ def is_exempt(request):
104
+ """Check if request should be exempt (either by path or view decorator)"""
105
+ return is_exempt_path(request.path) or is_view_exempt(request)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.8.5
3
+ Version: 0.1.8.6
4
4
  Summary: AI-powered Web Application Firewall
5
5
  Home-page: https://github.com/aayushgauba/aiwaf
6
6
  Author: Aayush Gauba
@@ -99,7 +99,26 @@ AIWAF_EXEMPT_PATHS = [
99
99
  ]
100
100
  ```
101
101
 
102
- All exempt paths are:
102
+ **Exempt Views (Decorator):**
103
+ Use the `@aiwaf_exempt` decorator to exempt specific views from all AI-WAF protection:
104
+
105
+ ```python
106
+ from aiwaf.decorators import aiwaf_exempt
107
+ from django.http import JsonResponse
108
+
109
+ @aiwaf_exempt
110
+ def my_api_view(request):
111
+ """This view will be exempt from all AI-WAF protection"""
112
+ return JsonResponse({"status": "success"})
113
+
114
+ # Works with class-based views too
115
+ @aiwaf_exempt
116
+ class MyAPIView(View):
117
+ def get(self, request):
118
+ return JsonResponse({"method": "GET"})
119
+ ```
120
+
121
+ All exempt paths and views are:
103
122
  - Skipped from keyword learning
104
123
  - Immune to AI blocking
105
124
  - Ignored in log training
@@ -1,11 +1,12 @@
1
1
  aiwaf/__init__.py,sha256=nQFpJ1YpX48snzLjEQCf8zD2YNh8v0b_kPTrXx8uBYc,46
2
2
  aiwaf/apps.py,sha256=nCez-Ptlv2kaEk5HenA8b1pATz1VfhrHP1344gwcY1A,142
3
3
  aiwaf/blacklist_manager.py,sha256=sM6uTH7zD6MOPGb0kzqV2aFut2vxKgft_UVeRJr7klw,392
4
- aiwaf/middleware.py,sha256=GmN9R6z1wx1g4coMoixGK_b3i4dwI5z0xBAIu4I7CoQ,10477
4
+ aiwaf/decorators.py,sha256=IUKOdM_gdroffImRZep1g1wT6gNqD10zGwcp28hsJCs,825
5
+ aiwaf/middleware.py,sha256=eMad-wvQWALkH2nIhjssU9Y-AqFleP3Gm0lRu3qE0Bw,9679
5
6
  aiwaf/models.py,sha256=XaG1pd_oZu3y-fw66u4wblGlWcUY9gvsTNKGD0kQk7Y,1672
6
7
  aiwaf/storage.py,sha256=bxCILzzvA1-q6nwclRE8WrfoRhe25H4VrsQDf0hl_lY,1903
7
- aiwaf/trainer.py,sha256=eJOHuJgIyqUIqmbYwnMWoR4fQQ8miTn64ASXMmShgI8,9120
8
- aiwaf/utils.py,sha256=RkEUWhhHy6tOk7V0UYv3cN4xhOR_7aBy9bjhwuV2cdA,1436
8
+ aiwaf/trainer.py,sha256=R00q_QQ1o2UmdIWMWNh847BGBrnI6j-hfjNalojfnhU,8494
9
+ aiwaf/utils.py,sha256=s-rtUrWQFVv-nuGxe2hz5-LLvB6TbZXKj6do46DwrkA,3376
9
10
  aiwaf/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  aiwaf/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
12
  aiwaf/management/commands/add_ipexemption.py,sha256=LWN21_ydqSjU3_hUnkou4Ciyrk_479zLvcKdWm8hkC0,988
@@ -14,8 +15,8 @@ aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMm
14
15
  aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
15
16
  aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
17
- aiwaf-0.1.8.5.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
18
- aiwaf-0.1.8.5.dist-info/METADATA,sha256=ef023TmhChIqMkNwTgEsyPODvDs44q9SXmQCYjxzoBs,7435
19
- aiwaf-0.1.8.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- aiwaf-0.1.8.5.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
21
- aiwaf-0.1.8.5.dist-info/RECORD,,
18
+ aiwaf-0.1.8.6.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
19
+ aiwaf-0.1.8.6.dist-info/METADATA,sha256=s6gux1GQJsbvphRXyVkVPm63_bVWIXcJcagWtBSlgpE,7955
20
+ aiwaf-0.1.8.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ aiwaf-0.1.8.6.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
22
+ aiwaf-0.1.8.6.dist-info/RECORD,,