arthexis 0.1.12__py3-none-any.whl → 0.1.14__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 arthexis might be problematic. Click here for more details.
- {arthexis-0.1.12.dist-info → arthexis-0.1.14.dist-info}/METADATA +222 -221
- arthexis-0.1.14.dist-info/RECORD +109 -0
- {arthexis-0.1.12.dist-info → arthexis-0.1.14.dist-info}/licenses/LICENSE +674 -674
- config/__init__.py +5 -5
- config/active_app.py +15 -15
- config/asgi.py +43 -29
- config/auth_app.py +7 -7
- config/celery.py +32 -25
- config/context_processors.py +67 -69
- config/horologia_app.py +7 -7
- config/loadenv.py +11 -11
- config/logging.py +59 -48
- config/middleware.py +25 -25
- config/offline.py +49 -49
- config/settings.py +691 -716
- config/settings_helpers.py +109 -0
- config/urls.py +171 -166
- config/wsgi.py +17 -17
- core/admin.py +3771 -2772
- core/admin_history.py +50 -50
- core/admindocs.py +151 -151
- core/apps.py +356 -272
- core/auto_upgrade.py +57 -57
- core/backends.py +265 -236
- core/changelog.py +342 -0
- core/entity.py +133 -133
- core/environment.py +61 -61
- core/fields.py +168 -168
- core/form_fields.py +75 -0
- core/github_helper.py +188 -25
- core/github_issues.py +178 -172
- core/github_repos.py +72 -0
- core/lcd_screen.py +78 -78
- core/liveupdate.py +25 -25
- core/log_paths.py +100 -100
- core/mailer.py +85 -85
- core/middleware.py +91 -91
- core/models.py +3609 -2672
- core/notifications.py +105 -105
- core/public_wifi.py +267 -227
- core/reference_utils.py +108 -108
- core/release.py +721 -350
- core/rfid_import_export.py +113 -0
- core/sigil_builder.py +149 -149
- core/sigil_context.py +20 -20
- core/sigil_resolver.py +315 -315
- core/system.py +752 -493
- core/tasks.py +408 -394
- core/temp_passwords.py +181 -181
- core/test_system_info.py +186 -139
- core/tests.py +2095 -1511
- core/tests_liveupdate.py +17 -17
- core/urls.py +11 -11
- core/user_data.py +641 -633
- core/views.py +2175 -1382
- core/widgets.py +213 -51
- core/workgroup_urls.py +17 -17
- core/workgroup_views.py +94 -94
- nodes/admin.py +1720 -898
- nodes/apps.py +87 -70
- nodes/backends.py +160 -160
- nodes/dns.py +203 -203
- nodes/feature_checks.py +133 -133
- nodes/lcd.py +165 -165
- nodes/models.py +1737 -1416
- nodes/reports.py +411 -411
- nodes/rfid_sync.py +195 -0
- nodes/signals.py +18 -0
- nodes/tasks.py +46 -46
- nodes/tests.py +3810 -2497
- nodes/urls.py +15 -13
- nodes/utils.py +121 -105
- nodes/views.py +683 -451
- ocpp/admin.py +948 -804
- ocpp/apps.py +25 -25
- ocpp/consumers.py +1565 -1342
- ocpp/evcs.py +844 -931
- ocpp/evcs_discovery.py +158 -158
- ocpp/models.py +917 -915
- ocpp/reference_utils.py +42 -42
- ocpp/routing.py +11 -9
- ocpp/simulator.py +745 -724
- ocpp/status_display.py +26 -0
- ocpp/store.py +601 -541
- ocpp/tasks.py +31 -31
- ocpp/test_export_import.py +130 -130
- ocpp/test_rfid.py +913 -702
- ocpp/tests.py +4445 -3485
- ocpp/transactions_io.py +189 -179
- ocpp/urls.py +50 -50
- ocpp/views.py +1479 -1151
- pages/admin.py +708 -536
- pages/apps.py +10 -10
- pages/checks.py +40 -40
- pages/context_processors.py +127 -119
- pages/defaults.py +13 -13
- pages/forms.py +198 -169
- pages/middleware.py +205 -153
- pages/models.py +607 -426
- pages/tests.py +2612 -2083
- pages/urls.py +25 -25
- pages/utils.py +12 -12
- pages/views.py +1165 -1120
- arthexis-0.1.12.dist-info/RECORD +0 -102
- nodes/actions.py +0 -70
- {arthexis-0.1.12.dist-info → arthexis-0.1.14.dist-info}/WHEEL +0 -0
- {arthexis-0.1.12.dist-info → arthexis-0.1.14.dist-info}/top_level.txt +0 -0
core/auto_upgrade.py
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
"""Helpers for managing the auto-upgrade scheduler."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from django.conf import settings
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
AUTO_UPGRADE_TASK_NAME = "auto-upgrade-check"
|
|
11
|
-
AUTO_UPGRADE_TASK_PATH = "core.tasks.check_github_updates"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def ensure_auto_upgrade_periodic_task(
|
|
15
|
-
sender=None, *, base_dir: Path | None = None, **kwargs
|
|
16
|
-
) -> None:
|
|
17
|
-
"""Ensure the auto-upgrade periodic task exists.
|
|
18
|
-
|
|
19
|
-
The function is signal-safe so it can be wired to Django's
|
|
20
|
-
``post_migrate`` hook. When called directly the ``sender`` and
|
|
21
|
-
``**kwargs`` parameters are ignored.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
del sender, kwargs # Unused when invoked as a Django signal handler.
|
|
25
|
-
|
|
26
|
-
if base_dir is None:
|
|
27
|
-
base_dir = Path(settings.BASE_DIR)
|
|
28
|
-
else:
|
|
29
|
-
base_dir = Path(base_dir)
|
|
30
|
-
|
|
31
|
-
lock_dir = base_dir / "locks"
|
|
32
|
-
mode_file = lock_dir / "auto_upgrade.lck"
|
|
33
|
-
if not mode_file.exists():
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
try: # pragma: no cover - optional dependency failures
|
|
37
|
-
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
|
38
|
-
from django.db.utils import OperationalError, ProgrammingError
|
|
39
|
-
except Exception:
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
interval_minutes = 5
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
schedule, _ = IntervalSchedule.objects.get_or_create(
|
|
47
|
-
every=interval_minutes, period=IntervalSchedule.MINUTES
|
|
48
|
-
)
|
|
49
|
-
PeriodicTask.objects.update_or_create(
|
|
50
|
-
name=AUTO_UPGRADE_TASK_NAME,
|
|
51
|
-
defaults={
|
|
52
|
-
"interval": schedule,
|
|
53
|
-
"task": AUTO_UPGRADE_TASK_PATH,
|
|
54
|
-
},
|
|
55
|
-
)
|
|
56
|
-
except (OperationalError, ProgrammingError): # pragma: no cover - DB not ready
|
|
57
|
-
return
|
|
1
|
+
"""Helpers for managing the auto-upgrade scheduler."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
AUTO_UPGRADE_TASK_NAME = "auto-upgrade-check"
|
|
11
|
+
AUTO_UPGRADE_TASK_PATH = "core.tasks.check_github_updates"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ensure_auto_upgrade_periodic_task(
|
|
15
|
+
sender=None, *, base_dir: Path | None = None, **kwargs
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Ensure the auto-upgrade periodic task exists.
|
|
18
|
+
|
|
19
|
+
The function is signal-safe so it can be wired to Django's
|
|
20
|
+
``post_migrate`` hook. When called directly the ``sender`` and
|
|
21
|
+
``**kwargs`` parameters are ignored.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
del sender, kwargs # Unused when invoked as a Django signal handler.
|
|
25
|
+
|
|
26
|
+
if base_dir is None:
|
|
27
|
+
base_dir = Path(settings.BASE_DIR)
|
|
28
|
+
else:
|
|
29
|
+
base_dir = Path(base_dir)
|
|
30
|
+
|
|
31
|
+
lock_dir = base_dir / "locks"
|
|
32
|
+
mode_file = lock_dir / "auto_upgrade.lck"
|
|
33
|
+
if not mode_file.exists():
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
try: # pragma: no cover - optional dependency failures
|
|
37
|
+
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
|
38
|
+
from django.db.utils import OperationalError, ProgrammingError
|
|
39
|
+
except Exception:
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
_mode = mode_file.read_text().strip() or "version"
|
|
43
|
+
interval_minutes = 5
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
schedule, _ = IntervalSchedule.objects.get_or_create(
|
|
47
|
+
every=interval_minutes, period=IntervalSchedule.MINUTES
|
|
48
|
+
)
|
|
49
|
+
PeriodicTask.objects.update_or_create(
|
|
50
|
+
name=AUTO_UPGRADE_TASK_NAME,
|
|
51
|
+
defaults={
|
|
52
|
+
"interval": schedule,
|
|
53
|
+
"task": AUTO_UPGRADE_TASK_PATH,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
except (OperationalError, ProgrammingError): # pragma: no cover - DB not ready
|
|
57
|
+
return
|
core/backends.py
CHANGED
|
@@ -1,236 +1,265 @@
|
|
|
1
|
-
"""Custom authentication backends for the core app."""
|
|
2
|
-
|
|
3
|
-
import contextlib
|
|
4
|
-
import ipaddress
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from django.
|
|
10
|
-
from django.
|
|
11
|
-
from django.
|
|
12
|
-
from
|
|
13
|
-
|
|
14
|
-
from .models import
|
|
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
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
if
|
|
82
|
-
return
|
|
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
|
-
return None
|
|
1
|
+
"""Custom authentication backends for the core app."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import ipaddress
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
from django.contrib.auth import get_user_model
|
|
11
|
+
from django.contrib.auth.backends import ModelBackend
|
|
12
|
+
from django.core.exceptions import DisallowedHost
|
|
13
|
+
from django.http.request import split_domain_port
|
|
14
|
+
from django_otp.plugins.otp_totp.models import TOTPDevice
|
|
15
|
+
|
|
16
|
+
from .models import EnergyAccount, RFID
|
|
17
|
+
from . import temp_passwords
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TOTP_DEVICE_NAME = "authenticator"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TOTPBackend(ModelBackend):
|
|
24
|
+
"""Authenticate using a TOTP code from an enrolled authenticator app."""
|
|
25
|
+
|
|
26
|
+
def authenticate(self, request, username=None, otp_token=None, **kwargs):
|
|
27
|
+
if not username or otp_token in (None, ""):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
token = str(otp_token).strip().replace(" ", "")
|
|
31
|
+
if not token:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
UserModel = get_user_model()
|
|
35
|
+
try:
|
|
36
|
+
user = UserModel._default_manager.get_by_natural_key(username)
|
|
37
|
+
except UserModel.DoesNotExist:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
if not user.is_active:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
device_qs = TOTPDevice.objects.filter(user=user, confirmed=True)
|
|
44
|
+
if TOTP_DEVICE_NAME:
|
|
45
|
+
device_qs = device_qs.filter(name=TOTP_DEVICE_NAME)
|
|
46
|
+
|
|
47
|
+
device = device_qs.order_by("-id").first()
|
|
48
|
+
if device is None:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
verified = device.verify_token(token)
|
|
53
|
+
except Exception:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
if not verified:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
user.otp_device = device
|
|
60
|
+
return user
|
|
61
|
+
|
|
62
|
+
def get_user(self, user_id):
|
|
63
|
+
UserModel = get_user_model()
|
|
64
|
+
try:
|
|
65
|
+
return UserModel._default_manager.get(pk=user_id)
|
|
66
|
+
except UserModel.DoesNotExist:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RFIDBackend:
|
|
71
|
+
"""Authenticate using a user's RFID."""
|
|
72
|
+
|
|
73
|
+
def authenticate(self, request, rfid=None, **kwargs):
|
|
74
|
+
if not rfid:
|
|
75
|
+
return None
|
|
76
|
+
rfid_value = str(rfid).strip().upper()
|
|
77
|
+
if not rfid_value:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
tag = RFID.objects.filter(rfid=rfid_value).first()
|
|
81
|
+
if not tag or not tag.allowed:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
command = (tag.external_command or "").strip()
|
|
85
|
+
if command:
|
|
86
|
+
env = os.environ.copy()
|
|
87
|
+
env["RFID_VALUE"] = rfid_value
|
|
88
|
+
env["RFID_LABEL_ID"] = str(tag.pk)
|
|
89
|
+
try:
|
|
90
|
+
completed = subprocess.run(
|
|
91
|
+
command,
|
|
92
|
+
shell=True,
|
|
93
|
+
check=False,
|
|
94
|
+
capture_output=True,
|
|
95
|
+
text=True,
|
|
96
|
+
env=env,
|
|
97
|
+
)
|
|
98
|
+
except Exception:
|
|
99
|
+
return None
|
|
100
|
+
if completed.returncode != 0:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
account = (
|
|
104
|
+
EnergyAccount.objects.filter(
|
|
105
|
+
rfids__pk=tag.pk, rfids__allowed=True, user__isnull=False
|
|
106
|
+
)
|
|
107
|
+
.select_related("user")
|
|
108
|
+
.first()
|
|
109
|
+
)
|
|
110
|
+
if account:
|
|
111
|
+
return account.user
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def get_user(self, user_id):
|
|
115
|
+
User = get_user_model()
|
|
116
|
+
try:
|
|
117
|
+
return User.objects.get(pk=user_id)
|
|
118
|
+
except User.DoesNotExist:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _collect_local_ip_addresses():
|
|
123
|
+
"""Return IP addresses assigned to the current machine."""
|
|
124
|
+
|
|
125
|
+
hosts = {socket.gethostname().strip()}
|
|
126
|
+
with contextlib.suppress(Exception):
|
|
127
|
+
hosts.add(socket.getfqdn().strip())
|
|
128
|
+
|
|
129
|
+
addresses = set()
|
|
130
|
+
for host in filter(None, hosts):
|
|
131
|
+
with contextlib.suppress(OSError):
|
|
132
|
+
_, _, ip_list = socket.gethostbyname_ex(host)
|
|
133
|
+
for candidate in ip_list:
|
|
134
|
+
with contextlib.suppress(ValueError):
|
|
135
|
+
addresses.add(ipaddress.ip_address(candidate))
|
|
136
|
+
with contextlib.suppress(OSError):
|
|
137
|
+
for info in socket.getaddrinfo(host, None, family=socket.AF_UNSPEC):
|
|
138
|
+
sockaddr = info[-1]
|
|
139
|
+
if not sockaddr:
|
|
140
|
+
continue
|
|
141
|
+
raw_address = sockaddr[0]
|
|
142
|
+
if isinstance(raw_address, bytes):
|
|
143
|
+
with contextlib.suppress(UnicodeDecodeError):
|
|
144
|
+
raw_address = raw_address.decode()
|
|
145
|
+
if isinstance(raw_address, str):
|
|
146
|
+
if "%" in raw_address:
|
|
147
|
+
raw_address = raw_address.split("%", 1)[0]
|
|
148
|
+
with contextlib.suppress(ValueError):
|
|
149
|
+
addresses.add(ipaddress.ip_address(raw_address))
|
|
150
|
+
return tuple(sorted(addresses, key=str))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LocalhostAdminBackend(ModelBackend):
|
|
154
|
+
"""Allow default admin credentials only from local networks."""
|
|
155
|
+
|
|
156
|
+
_ALLOWED_NETWORKS = (
|
|
157
|
+
ipaddress.ip_network("::1/128"),
|
|
158
|
+
ipaddress.ip_network("127.0.0.0/8"),
|
|
159
|
+
ipaddress.ip_network("10.42.0.0/16"),
|
|
160
|
+
ipaddress.ip_network("192.168.0.0/16"),
|
|
161
|
+
)
|
|
162
|
+
_CONTROL_ALLOWED_NETWORKS = (ipaddress.ip_network("10.0.0.0/8"),)
|
|
163
|
+
_LOCAL_IPS = _collect_local_ip_addresses()
|
|
164
|
+
|
|
165
|
+
def _iter_allowed_networks(self):
|
|
166
|
+
yield from self._ALLOWED_NETWORKS
|
|
167
|
+
if getattr(settings, "NODE_ROLE", "") == "Control":
|
|
168
|
+
yield from self._CONTROL_ALLOWED_NETWORKS
|
|
169
|
+
|
|
170
|
+
def authenticate(self, request, username=None, password=None, **kwargs):
|
|
171
|
+
if username == "admin" and password == "admin" and request is not None:
|
|
172
|
+
try:
|
|
173
|
+
host = request.get_host()
|
|
174
|
+
except DisallowedHost:
|
|
175
|
+
return None
|
|
176
|
+
host, _port = split_domain_port(host)
|
|
177
|
+
if host.startswith("[") and host.endswith("]"):
|
|
178
|
+
host = host[1:-1]
|
|
179
|
+
try:
|
|
180
|
+
ipaddress.ip_address(host)
|
|
181
|
+
except ValueError:
|
|
182
|
+
return None
|
|
183
|
+
forwarded = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
184
|
+
if forwarded:
|
|
185
|
+
remote = forwarded.split(",")[0].strip()
|
|
186
|
+
else:
|
|
187
|
+
remote = request.META.get("REMOTE_ADDR", "")
|
|
188
|
+
try:
|
|
189
|
+
ip = ipaddress.ip_address(remote)
|
|
190
|
+
except ValueError:
|
|
191
|
+
return None
|
|
192
|
+
allowed = any(ip in net for net in self._iter_allowed_networks())
|
|
193
|
+
if not allowed and ip in self._LOCAL_IPS:
|
|
194
|
+
allowed = True
|
|
195
|
+
if not allowed:
|
|
196
|
+
return None
|
|
197
|
+
User = get_user_model()
|
|
198
|
+
user, created = User.all_objects.get_or_create(
|
|
199
|
+
username="admin",
|
|
200
|
+
defaults={
|
|
201
|
+
"is_staff": True,
|
|
202
|
+
"is_superuser": True,
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
if not created and not user.is_active:
|
|
206
|
+
return None
|
|
207
|
+
arthexis_user = (
|
|
208
|
+
User.all_objects.filter(username="arthexis").exclude(pk=user.pk).first()
|
|
209
|
+
)
|
|
210
|
+
if created:
|
|
211
|
+
if arthexis_user and user.operate_as_id is None:
|
|
212
|
+
user.operate_as = arthexis_user
|
|
213
|
+
user.set_password("admin")
|
|
214
|
+
user.save()
|
|
215
|
+
elif not user.check_password("admin"):
|
|
216
|
+
return None
|
|
217
|
+
elif arthexis_user and user.operate_as_id is None:
|
|
218
|
+
user.operate_as = arthexis_user
|
|
219
|
+
user.save(update_fields=["operate_as"])
|
|
220
|
+
return user
|
|
221
|
+
return super().authenticate(request, username, password, **kwargs)
|
|
222
|
+
|
|
223
|
+
def get_user(self, user_id):
|
|
224
|
+
User = get_user_model()
|
|
225
|
+
try:
|
|
226
|
+
return User.all_objects.get(pk=user_id)
|
|
227
|
+
except User.DoesNotExist:
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TempPasswordBackend(ModelBackend):
|
|
232
|
+
"""Authenticate using a temporary password stored in a lockfile."""
|
|
233
|
+
|
|
234
|
+
def authenticate(self, request, username=None, password=None, **kwargs):
|
|
235
|
+
if not username or not password:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
UserModel = get_user_model()
|
|
239
|
+
manager = getattr(UserModel, "all_objects", UserModel._default_manager)
|
|
240
|
+
try:
|
|
241
|
+
user = manager.get_by_natural_key(username)
|
|
242
|
+
except UserModel.DoesNotExist:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
entry = temp_passwords.load_temp_password(user.username)
|
|
246
|
+
if entry is None:
|
|
247
|
+
return None
|
|
248
|
+
if entry.is_expired:
|
|
249
|
+
temp_passwords.discard_temp_password(user.username)
|
|
250
|
+
return None
|
|
251
|
+
if not entry.check_password(password):
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
if not user.is_active:
|
|
255
|
+
user.is_active = True
|
|
256
|
+
user.save(update_fields=["is_active"])
|
|
257
|
+
return user
|
|
258
|
+
|
|
259
|
+
def get_user(self, user_id):
|
|
260
|
+
UserModel = get_user_model()
|
|
261
|
+
manager = getattr(UserModel, "all_objects", UserModel._default_manager)
|
|
262
|
+
try:
|
|
263
|
+
return manager.get(pk=user_id)
|
|
264
|
+
except UserModel.DoesNotExist:
|
|
265
|
+
return None
|