prune_captcha 1.10.0__py3-none-any.whl → 1.11.0__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.
- captcha_prune/urls.py +2 -2
- captcha_prune/views.py +40 -22
- {prune_captcha-1.10.0.dist-info → prune_captcha-1.11.0.dist-info}/METADATA +16 -19
- {prune_captcha-1.10.0.dist-info → prune_captcha-1.11.0.dist-info}/RECORD +6 -7
- {prune_captcha-1.10.0.dist-info → prune_captcha-1.11.0.dist-info}/WHEEL +1 -1
- captcha_prune/utils.py +0 -68
- {prune_captcha-1.10.0.dist-info → prune_captcha-1.11.0.dist-info}/top_level.txt +0 -0
captcha_prune/urls.py
CHANGED
@@ -5,6 +5,6 @@ from captcha_prune.views import create_captcha_view, verify_captcha_view
|
|
5
5
|
app_name = "captcha"
|
6
6
|
|
7
7
|
urlpatterns = [
|
8
|
-
path("", create_captcha_view, name="create-captcha"),
|
9
|
-
path("
|
8
|
+
path("create/", create_captcha_view, name="create-captcha"),
|
9
|
+
path("verify/", verify_captcha_view, name="verify-captcha"),
|
10
10
|
]
|
captcha_prune/views.py
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
1
|
+
import os
|
2
|
+
import random
|
3
|
+
|
4
|
+
from django.conf import settings
|
5
|
+
from django.contrib import messages
|
6
|
+
from django.http import HttpRequest
|
2
7
|
from django.shortcuts import get_object_or_404
|
3
8
|
from django.views.decorators.csrf import csrf_exempt
|
4
9
|
from django.views.decorators.http import require_GET, require_POST
|
@@ -10,36 +15,49 @@ from commons.decorators import use_payload
|
|
10
15
|
|
11
16
|
@require_POST
|
12
17
|
@csrf_exempt
|
13
|
-
def create_captcha_view(request: HttpRequest) ->
|
18
|
+
def create_captcha_view(request: HttpRequest) -> dict:
|
14
19
|
captcha = Captcha.objects.create()
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
request.session["puzzle_uuid"] = captcha.uuid
|
21
|
+
|
22
|
+
_, _, puzzle_images_path = settings.PUZZLE_IMAGE_STATIC_PATH.rpartition("static/")
|
23
|
+
puzzle_images = [
|
24
|
+
f
|
25
|
+
for f in os.listdir(settings.PUZZLE_IMAGE_STATIC_PATH)
|
26
|
+
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp"))
|
27
|
+
]
|
28
|
+
selected_image = random.choice(puzzle_images)
|
29
|
+
|
30
|
+
return {
|
31
|
+
"uuid": captcha.uuid,
|
32
|
+
"width": captcha.width,
|
33
|
+
"height": captcha.height,
|
34
|
+
"piece_width": captcha.piece_width,
|
35
|
+
"piece_height": captcha.piece_height,
|
36
|
+
"pos_x_solution": captcha.pos_x_solution,
|
37
|
+
"pos_y_solution": captcha.pos_y_solution,
|
38
|
+
"piece_pos_x": captcha.piece_pos_x,
|
39
|
+
"piece_pos_y": captcha.piece_pos_y,
|
40
|
+
"image": f"{puzzle_images_path}{selected_image}",
|
41
|
+
}
|
29
42
|
|
30
43
|
|
31
44
|
@require_GET
|
32
45
|
@use_payload(PuzzleAnswerPayload)
|
33
|
-
def verify_captcha_view(
|
34
|
-
request: HttpRequest, uuid: str, payload: PuzzleAnswerPayload
|
35
|
-
) -> HttpResponse:
|
36
|
-
captcha = get_object_or_404(Captcha, uuid=uuid)
|
46
|
+
def verify_captcha_view(request: HttpRequest, payload: PuzzleAnswerPayload) -> bool:
|
37
47
|
pos_x_answer = payload.pos_x_answer
|
38
48
|
pos_y_answer = payload.pos_y_answer
|
49
|
+
|
50
|
+
puzzle_uuid = request.session.get("puzzle_uuid")
|
51
|
+
if not puzzle_uuid:
|
52
|
+
messages.error(request, "La session a expiré.")
|
53
|
+
return False
|
54
|
+
captcha = get_object_or_404(Captcha, uuid=puzzle_uuid)
|
55
|
+
|
39
56
|
if (
|
40
57
|
abs(captcha.pos_x_solution - pos_x_answer) <= captcha.precision
|
41
58
|
and abs(captcha.pos_y_solution - pos_y_answer) <= captcha.precision
|
42
59
|
):
|
43
|
-
return
|
60
|
+
return True
|
44
61
|
else:
|
45
|
-
|
62
|
+
messages.error(request, "Captcha incorrect. Veuillez réessayer.")
|
63
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: prune_captcha
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.11.0
|
4
4
|
Summary: A tool to protect formulaire from spam.
|
5
5
|
Author-email: Arnout <bastien@prune.sh>
|
6
6
|
Project-URL: Made_by, https://prune.sh/
|
@@ -68,14 +68,14 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
|
|
68
68
|
|
69
69
|
- GET request (form display)
|
70
70
|
|
71
|
-
- Use
|
71
|
+
- Use create_captcha_view to generate the captcha data:
|
72
72
|
|
73
73
|
```python
|
74
|
-
from captcha_prune.
|
74
|
+
from captcha_prune.views import create_captcha_view
|
75
75
|
```
|
76
76
|
|
77
77
|
```python
|
78
|
-
puzzle =
|
78
|
+
puzzle = create_captcha_view(request)
|
79
79
|
```
|
80
80
|
|
81
81
|
- Passes the data into the context under the puzzle variable:
|
@@ -96,25 +96,23 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
|
|
96
96
|
|
97
97
|
- POST request (form submission)
|
98
98
|
|
99
|
-
- Use
|
99
|
+
- Use verify_captcha_view to validate the captcha:
|
100
100
|
|
101
101
|
```python
|
102
|
-
from captcha_prune.
|
102
|
+
from captcha_prune.views import verify_captcha_view
|
103
103
|
```
|
104
104
|
|
105
105
|
```python
|
106
|
-
response =
|
107
|
-
request, redirect("/"), redirect("website:contact-page"), form
|
108
|
-
)
|
106
|
+
response = verify_captcha_view(request)
|
109
107
|
```
|
110
108
|
|
111
|
-
-
|
109
|
+
- True if the captcha is correct, else False.
|
112
110
|
|
113
111
|
- Redirects in case of expired session or incorrect captcha:
|
114
112
|
|
115
113
|
```python
|
116
|
-
if response is
|
117
|
-
return
|
114
|
+
if response is False:
|
115
|
+
return redirect("website:contact-page")
|
118
116
|
```
|
119
117
|
|
120
118
|
### Example
|
@@ -124,18 +122,16 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
|
|
124
122
|
if request.method == "POST":
|
125
123
|
form = ContactForm(request.POST)
|
126
124
|
if form.is_valid():
|
127
|
-
response =
|
128
|
-
|
129
|
-
)
|
130
|
-
if response is not None:
|
131
|
-
return response
|
125
|
+
response = verify_captcha_view(request)
|
126
|
+
if response is False:
|
127
|
+
return redirect("website:contact-page")
|
132
128
|
messages.success(request, "Formulaire soumis avec succès.")
|
133
129
|
return redirect("/")
|
134
130
|
else:
|
135
|
-
puzzle =
|
131
|
+
puzzle = create_captcha_view(request)
|
136
132
|
else:
|
137
133
|
form = ContactForm()
|
138
|
-
puzzle =
|
134
|
+
puzzle = create_captcha_view(request)
|
139
135
|
return render(
|
140
136
|
request,
|
141
137
|
"website/pages/contact/page.html",
|
@@ -147,6 +143,7 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
|
|
147
143
|
|
148
144
|
| Version | Date | Notes |
|
149
145
|
| ------- | ---------- | ---------------------------------- |
|
146
|
+
| 1.11.0 | 2025-05-21 | removed utils |
|
150
147
|
| 1.10.0 | 2025-05-20 | fix documentation, removed ... |
|
151
148
|
| 1.9.0 | 2025-05-20 | puzzle images path fixed |
|
152
149
|
| 1.8.0 | 2025-05-20 | added migrations |
|
@@ -4,9 +4,8 @@ captcha_prune/asgi.py,sha256=wi2rRLFltXOZyve8mAB_E8udaFyONOf5N42WrWIQX8M,403
|
|
4
4
|
captcha_prune/models.py,sha256=O8nxhVfPat3oaDGZzd88kQHkGayQIWZ_dOO9uu1R6P4,1240
|
5
5
|
captcha_prune/payloads.py,sha256=PwR48xKg9YEgGgoJmGe7ywwFPCqM4gumomyFunn-Ems,115
|
6
6
|
captcha_prune/settings.py,sha256=YEZHEjYYYK-cH3gSLIR3a7g4Bypnuf_5eMTTcm719CE,3395
|
7
|
-
captcha_prune/urls.py,sha256=
|
8
|
-
captcha_prune/
|
9
|
-
captcha_prune/views.py,sha256=BzjQfXLwYx2YSWODrnGpokqU8XeC-9QORivzWKxYofY,1539
|
7
|
+
captcha_prune/urls.py,sha256=0G1O0CO53iBW_QT5vlvyN1UzsWxxZiiooqApx6f3MeU,274
|
8
|
+
captcha_prune/views.py,sha256=Y4F3zhq5uYpPuHs-0eI8dpn_-02Vbqw-JURK3fTE7tg,2095
|
10
9
|
captcha_prune/wsgi.py,sha256=fukA_iiCT4FFzcJ0QX2Zr_HNddqdq-ln3ien2UWcCTA,403
|
11
10
|
captcha_prune/migrations/0001_initial.py,sha256=QMTaeSIxPociSrV4r89zfsbxMwMu4qpL16LT3yPgcaw,831
|
12
11
|
captcha_prune/migrations/0002_remove_captcha_created_at_remove_captcha_pos_x_and_more.py,sha256=xyyE_G3t8kJqFvKuNWFRTyTyyD1kzvfeSgbkTQ9DO8A,2099
|
@@ -17,7 +16,7 @@ commons/decorators.py,sha256=nU_3SyxENcWDdxo03VyNYEvoevplC6ig7BFogY8apNs,1306
|
|
17
16
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
17
|
tests/captcha_prune/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
18
|
tests/captcha_prune/test_views.py,sha256=h-Mrdiprh71s7kIJPYAt6xv4G_S7vp51WCpAzy4CZ6U,2364
|
20
|
-
prune_captcha-1.
|
21
|
-
prune_captcha-1.
|
22
|
-
prune_captcha-1.
|
23
|
-
prune_captcha-1.
|
19
|
+
prune_captcha-1.11.0.dist-info/METADATA,sha256=moZ4qiAgMV2a0wkTqUj8EPkISZgck95t6n_hvE6cVFQ,4259
|
20
|
+
prune_captcha-1.11.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
21
|
+
prune_captcha-1.11.0.dist-info/top_level.txt,sha256=EB4h0WF_YGF7kAWB0XtqmuloOhkL5pC71CzgEVYam7w,28
|
22
|
+
prune_captcha-1.11.0.dist-info/RECORD,,
|
captcha_prune/utils.py
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import random
|
3
|
-
from urllib.parse import urlencode
|
4
|
-
|
5
|
-
import requests
|
6
|
-
from django.conf import settings
|
7
|
-
from django.contrib import messages
|
8
|
-
from django.http import HttpRequest, HttpResponse
|
9
|
-
from django.urls import reverse
|
10
|
-
|
11
|
-
|
12
|
-
def create_and_get_puzzle(request: HttpRequest) -> HttpResponse | dict:
|
13
|
-
_, _, puzzle_images_path = settings.PUZZLE_IMAGE_STATIC_PATH.rpartition("static/")
|
14
|
-
try:
|
15
|
-
response = requests.post(
|
16
|
-
request.build_absolute_uri(reverse("captcha:create-captcha"))
|
17
|
-
).json()
|
18
|
-
except requests.RequestException:
|
19
|
-
return HttpResponse(status=502)
|
20
|
-
request.session["puzzle_uuid"] = response["uuid"]
|
21
|
-
puzzle_images = [
|
22
|
-
f
|
23
|
-
for f in os.listdir(settings.PUZZLE_IMAGE_STATIC_PATH)
|
24
|
-
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif"))
|
25
|
-
]
|
26
|
-
selected_image = random.choice(puzzle_images)
|
27
|
-
return {
|
28
|
-
"uuid": response["uuid"],
|
29
|
-
"width": response["width"],
|
30
|
-
"height": response["height"],
|
31
|
-
"piece_width": response["piece_width"],
|
32
|
-
"piece_height": response["piece_height"],
|
33
|
-
"pos_x_solution": response["pos_x_solution"],
|
34
|
-
"pos_y_solution": response["pos_y_solution"],
|
35
|
-
"piece_pos_x": response["piece_pos_x"],
|
36
|
-
"piece_pos_y": response["piece_pos_y"],
|
37
|
-
"image": f"{puzzle_images_path}{selected_image}",
|
38
|
-
}
|
39
|
-
|
40
|
-
|
41
|
-
def verify_captcha(
|
42
|
-
request: HttpRequest,
|
43
|
-
session_expired: HttpResponse,
|
44
|
-
incorrect_captcha: HttpResponse,
|
45
|
-
form,
|
46
|
-
) -> HttpResponse | None:
|
47
|
-
puzzle_uuid = request.session.get("puzzle_uuid")
|
48
|
-
if not puzzle_uuid:
|
49
|
-
messages.error(request, "La session a expiré.")
|
50
|
-
return session_expired
|
51
|
-
try:
|
52
|
-
base_url = request.build_absolute_uri(
|
53
|
-
reverse("captcha:verify-captcha", kwargs={"uuid": puzzle_uuid}),
|
54
|
-
)
|
55
|
-
data = {
|
56
|
-
"pos_x_answer": request.POST.get("pos_x_answer"),
|
57
|
-
"pos_y_answer": request.POST.get("pos_y_answer"),
|
58
|
-
}
|
59
|
-
query_string = urlencode(data)
|
60
|
-
full_url = f"{base_url}?{query_string}"
|
61
|
-
response = requests.get(full_url)
|
62
|
-
if response.status_code == 401:
|
63
|
-
messages.error(request, "Captcha incorrect. Veuillez réessayer.")
|
64
|
-
return incorrect_captcha
|
65
|
-
del form.fields["pos_x_answer"]
|
66
|
-
del form.fields["pos_y_answer"]
|
67
|
-
except requests.RequestException:
|
68
|
-
return HttpResponse(status=502)
|
File without changes
|