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.

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