django-restit 4.1.75__py3-none-any.whl → 4.1.77__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.
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.1.4 on 2023-11-28 23:26
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('account', '0016_authsession_buid'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameField(
14
+ model_name='member',
15
+ old_name='requires_topt',
16
+ new_name='requires_totp',
17
+ ),
18
+ ]
account/models/member.py CHANGED
@@ -68,7 +68,7 @@ class User(AbstractUser, RestModel):
68
68
  class Member(User, RestModel, MetaDataModel):
69
69
  class RestMeta:
70
70
  NO_SHOW_FIELDS = ["password", "auth_code", "security_token"]
71
- SEARCH_FIELDS = ["username", "email", "first_name", "last_name", "display_name"]
71
+ SEARCH_FIELDS = ["username", "email", "first_name", "last_name", "display_name", "phone_number"]
72
72
  VIEW_PERMS = ["view_members", "manage_members", "manage_users", "owner"]
73
73
  SAVE_PERMS = ["invite_members", "manage_members", "manage_users", "owner"]
74
74
  LIST_DEFAULT_FILTERS = {
@@ -125,13 +125,13 @@ class Member(User, RestModel, MetaDataModel):
125
125
  'is_blocked',
126
126
  'is_staff',
127
127
  'is_superuser',
128
- 'requires_topt',
128
+ 'requires_totp',
129
129
  'last_login',
130
130
  'last_activity',
131
131
  ('date_joined', 'created'),
132
132
  ("hasLoggedIn", "has_logged_in"),
133
133
  'avatar',
134
- 'has_topt',
134
+ 'has_totp',
135
135
  'auth_token'
136
136
  ],
137
137
  "extra": ["metadata", "password_expires_in"],
@@ -152,7 +152,7 @@ class Member(User, RestModel, MetaDataModel):
152
152
 
153
153
  password_changed = models.DateTimeField(blank=True, null=True, default=None)
154
154
  last_activity = models.DateTimeField(blank=True, null=True, default=None)
155
- requires_topt = models.BooleanField(blank=True, null=True, default=False)
155
+ requires_totp = models.BooleanField(blank=True, null=True, default=False)
156
156
 
157
157
  def __str__(self):
158
158
  return self.username
@@ -214,9 +214,10 @@ class Member(User, RestModel, MetaDataModel):
214
214
  return None
215
215
 
216
216
  @property
217
- def has_topt(self):
218
- token = self.getSecretProperty("totp_token", default=None)
219
- return token is not None
217
+ def has_totp(self):
218
+ token = self.getProperty("totp_token", category="secrets", default=None)
219
+ verified = self.getProperty("totp_verified", default=False)
220
+ return token is not None and verified
220
221
 
221
222
  @property
222
223
  def password_expires_in(self):
@@ -667,7 +668,7 @@ class Member(User, RestModel, MetaDataModel):
667
668
 
668
669
  # time based one time passwords / GOOGLE Authenticator
669
670
  def totp_getSecret(self, reset=False):
670
- token = self.getProperty("totp_token", category="secrets", default=None)
671
+ token = self.getSecretProperty("totp_token", default=None)
671
672
  if token is None or reset:
672
673
  token = pyotp.random_base32()
673
674
  self.setProperty("totp_token", token, category="secrets")
account/periodic.py CHANGED
@@ -8,9 +8,9 @@ from sessionlog.models import SessionLog
8
8
  def run_cleanup_tokens(force=False, verbose=False, now=None):
9
9
  # we want to nuke invite tokens every 15 minutes
10
10
  # we do not want to do this if using invite
11
- stale = datetime.now() - timedelta(hours=24)
12
- qset = Member.objects.filter(auth_token__isnull=False).filter(modified__lte=stale)
13
- qset.update(auth_token=None)
11
+ stale = datetime.now() - timedelta(hours=48)
12
+ qset = Member.objects.filter(auth_code__isnull=False).filter(modified__lte=stale)
13
+ qset.update(auth_code=None)
14
14
 
15
15
  # lets prune old non active sessions
16
16
  SessionLog.Clean(limit=10000)
account/rpc/auth.py CHANGED
@@ -38,13 +38,24 @@ def jwt_login(request):
38
38
  return restStatus(request, False, error="Password and/or Username is incorrect", error_code=422)
39
39
  password = request.DATA.get('password', None)
40
40
  member.canLogin(request) # throws exception if cannot login
41
+ if member.requires_totp:
42
+ resp = checkForTOTP(request, member)
43
+ if resp is not None:
44
+ return resp
41
45
  if not member.login(request=request, password=password, use_jwt=True):
42
46
  member.log("login_failed", "incorrect password", request, method="login", level=31)
43
- return restStatus(request, False, error="Password or Username is incorrect", error_code=401)
47
+ return restStatus(request, False, error="Invalid Credentials", error_code=401)
48
+ return on_complete_jwt(request, member)
49
+
50
+
51
+ def on_complete_jwt(request, member):
44
52
  if member.security_token is None or member.security_token == JWT_KEY:
45
53
  member.refreshSecurityToken()
46
- member.log("jwt_login", "jwt login succesful", request, method="login", level=7)
47
-
54
+
55
+ member.log(
56
+ "jwt_login", "jwt login succesful",
57
+ request, method="login", level=7)
58
+
48
59
  device_id = request.DATA.get(["device_id", "deviceID"])
49
60
  token = JWToken(user_id=member.pk, key=member.security_token, device_id=device_id)
50
61
 
@@ -58,7 +69,12 @@ def jwt_login(request):
58
69
  am.MemberDevice.register(request, member, device_id)
59
70
 
60
71
  request.jwt_token = token.access_token # this tells the middleware to store in cookie
61
- return restGet(request, dict(access=token.access_token, refresh=token.refresh_token, id=member.pk))
72
+ return restGet(
73
+ request,
74
+ dict(
75
+ access=token.access_token,
76
+ refresh=token.refresh_token,
77
+ id=member.pk))
62
78
 
63
79
 
64
80
  @rd.urlPOST(r'^jwt/logout$')
@@ -107,19 +123,38 @@ def getMemberByUsername(username):
107
123
  return member
108
124
 
109
125
 
126
+ def checkForTOTP(request, member):
127
+ if not member.has_totp and not member.phone_number:
128
+ member.reportIncident(
129
+ "account", f"{member.username} TOTP not set", level=8,
130
+ error_code=455,
131
+ request=request)
132
+ return None
133
+ if not member.has_totp:
134
+ # we have a phone number so tell them to login with code
135
+ # they will need to request a code
136
+ return restStatus(
137
+ request, False, error=member.phone_number[-4:],
138
+ error_code=454)
139
+ totp_code = request.DATA.get("totp_code", None)
140
+ if totp_code is None:
141
+ # member.log("login_blocked", "requires MFA (TOTP)", request, method="login", level=31)
142
+ return restStatus(request, False, error="Requires MFA (TOTP)", error_code=455)
143
+ if not member.totp_verify(totp_code):
144
+ member.log("login_blocked", "Invalid MFA code", request, method="login", level=31)
145
+ return restStatus(request, False, error="Invalid Credentials", error_code=456)
146
+ return None
147
+
148
+
110
149
  def member_login_uname_pword(request, username, password):
111
150
  member = getMemberByUsername(username)
112
151
  if not member:
113
152
  return restStatus(request, False, error="Password or Username is not correct", error_code=422)
114
153
  member.canLogin(request) # throws exception if cannot login
115
154
  if member.requires_topt:
116
- totp_code = request.DATA.get("totp_code", None)
117
- if totp_code is None:
118
- member.log("login_blocked", "requires MFA (TOTP)", request, method="login", level=31)
119
- return restStatus(request, False, error="requires MFA (TOTP)", error_code=455)
120
- if not member.totp_verify(totp_code):
121
- member.log("login_blocked", "invalid MFA code", request, method="login", level=31)
122
- return restStatus(request, False, error="invalid MFA code", error_code=456)
155
+ resp = checkForTOTP(request, member)
156
+ if resp is not None:
157
+ return resp
123
158
  if not member.login(request=request, password=password, use_jwt=False):
124
159
  member.log("login_failed", "incorrect password", request, method="login", level=31)
125
160
  return restStatus(request, False, error="Password or Username is incorrect", error_code=401)
@@ -157,11 +192,7 @@ def member_login_uname_code(request, username, auth_code):
157
192
  if request.DATA.get("auth_method") == "basic":
158
193
  return restGet(request, dict(id=member.pk, session_key=request.session.session_key))
159
194
 
160
- # we still force the user to use JWT after code login
161
- if member.security_token is None:
162
- member.refreshSecurityToken()
163
- token = JWToken(user_id=member.pk, key=member.security_token)
164
- return restGet(request, dict(access=token.access_token, refresh=token.refresh_token, id=member.pk))
195
+ return on_complete_jwt(request, member)
165
196
 
166
197
 
167
198
  @rd.url(r'^logout$')
@@ -196,22 +227,24 @@ def is_member_logged_in(request):
196
227
  return restStatus(request, False)
197
228
 
198
229
 
199
- @rd.urlPOST (r'^forgot$')
200
- @rd.urlPOST (r'^forget/$')
230
+ @rd.urlPOST('mfa/request_code')
201
231
  @rd.never_cache
202
- def member_forgot_password(request):
203
- """
204
- | param: username = use the username as the lookup
205
- | param: email = use the email as the lookup
232
+ def member_request_code(request):
233
+ member = member_from_request(request)
234
+ resp = member_check_can_login(request, member)
235
+ if resp is not None:
236
+ return resp
237
+ return member_forgot_password_code(request, member)
206
238
 
207
- | Return: status + error
208
239
 
209
- | Send fgroupet password reset instructions
210
- """
240
+ def member_from_request(request):
211
241
  username = request.DATA.get('username', None)
212
242
  if not username:
213
- return restStatus(request, False, error="Username is required")
214
- member = getMemberByUsername(username)
243
+ raise restPermissionDenied("Username is required")
244
+ return getMemberByUsername(username)
245
+
246
+
247
+ def member_check_can_login(request, member):
215
248
  if not member:
216
249
  return restStatus(request, False, error="Password or Username is incorrect", error_code=422)
217
250
  if not member.is_active:
@@ -220,6 +253,24 @@ def member_forgot_password(request):
220
253
  if member.is_blocked:
221
254
  member.log("login_blocked", "account is locked out", request, method="login", level=31)
222
255
  return restStatus(request, False, error="Account locked out", error_code=411)
256
+ return None
257
+
258
+
259
+ @rd.urlPOST('forgot')
260
+ @rd.never_cache
261
+ def member_forgot_password(request):
262
+ """
263
+ | param: username = use the username as the lookup
264
+ | param: email = use the email as the lookup
265
+
266
+ | Return: status + error
267
+
268
+ | Send fgroupet password reset instructions
269
+ """
270
+ member = member_from_request(request)
271
+ resp = member_check_can_login(request, member)
272
+ if resp is not None:
273
+ return resp
223
274
 
224
275
  if request.DATA.get("use_code", False):
225
276
  return member_forgot_password_code(request, member)
@@ -264,7 +315,7 @@ def member_forgot_password_code(request, member):
264
315
 
265
316
 
266
317
  # time based one time passwords
267
- @rd.urlGET(r'^totp/qrcode$')
318
+ @rd.urlGET('totp/qrcode')
268
319
  @rd.login_required
269
320
  def totp_qrcode(request):
270
321
  token = request.member.getProperty("totp_token", category="secrets", default=None)
@@ -301,6 +352,7 @@ def totp_verify(request):
301
352
  return restPermissionDenied(request, "invalid code format")
302
353
  if not request.member.totp_verify(code):
303
354
  return restPermissionDenied(request, "invalid code")
355
+ request.member.setProperty("totp_verified", 1)
304
356
  return restStatus(request, True)
305
357
 
306
358
 
account/rpc/group.py CHANGED
@@ -46,9 +46,9 @@ def rest_on_group_invite(request, group_id=None):
46
46
  if not data or not data.email:
47
47
  return rv.restPermissionDenied(request, "missing fields")
48
48
  if data.email:
49
- member = Member.GetMember(data.email.lower())
49
+ member = Member.GetMember(data.email.lower().strip())
50
50
  elif data.username:
51
- member = Member.GetMember(data.username)
51
+ member = Member.GetMember(data.username.lower().strip())
52
52
  elif data.phone_number:
53
53
  member = Member.GetMemberByPhone(data.phone_number)
54
54
  if member is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-restit
3
- Version: 4.1.75
3
+ Version: 4.1.77
4
4
  Summary: A Rest Framework for DJANGO
5
5
  License: MIT
6
6
  Author: Ian Starnes
@@ -16,24 +16,25 @@ account/migrations/0013_memberdevice_ip.py,sha256=5Dxm5uNy7D1RnZeTYRYhU_7WlDQCme
16
16
  account/migrations/0014_alter_notificationmemberrecord_member.py,sha256=q3CHimwbhTk8rHkqaaOOcNrsHE7hSkVL7JDH6KmxWZs,541
17
17
  account/migrations/0015_memberdevice_buid.py,sha256=O3M3mS_O1zn4gQahC3ebRu8tMySo3cUbfO3X1-vLFMk,460
18
18
  account/migrations/0016_authsession_buid.py,sha256=wZdiH_87Ik3jAXYUgtafeAo9IbJq35xDVOlQ_UiTC8k,424
19
+ account/migrations/0017_rename_requires_topt_member_requires_totp.py,sha256=GksGiF7OQDV2RihyC2OTBzSmDwzCzenThkNs6FKni4M,375
19
20
  account/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
21
  account/models/__init__.py,sha256=OxQbDkVcZogwgNj3bQWljOxAkkt15KgBYVrsumzek8Q,341
21
22
  account/models/device.py,sha256=XipNpByreGubB5-d4ZBOoIV5Xw14b2Btcgn6fXz8HAc,4105
22
23
  account/models/feeds.py,sha256=4n4Mv8HjcXpUmMPWafHlsGbVQ1fDKdtblL1hp30sDrg,1437
23
24
  account/models/group.py,sha256=X_ij8moxmtH9qwH3w2EL8siuy-prsyxQVv1JIhBp3hI,19399
24
25
  account/models/legacy.py,sha256=zYdtv4LC0ooxPVqWM-uToPwV-lYWQLorSE6p6yn1xDw,2720
25
- account/models/member.py,sha256=kyPnmRRG-LF9q9bOfmaGkuPHnePu8bR7wKfhlqy6Xoo,48477
26
+ account/models/member.py,sha256=7z6RAW9vVl7FX1q6KfW02I6VvAzsxkJy_i2ULYfBNHo,48574
26
27
  account/models/membership.py,sha256=l7suW6kRCfa3KqdbqjOIO2jVhxjkPbEENdJIlj2WomY,7378
27
28
  account/models/notify.py,sha256=YnZujSHJHY7B09e6FIyZIEJRWLPYk1Sk1e92tFzB1IA,12078
28
29
  account/models/session.py,sha256=swhxcx_lWzOpGCmfifXoACgWYqmKmS7eZsD9s9jSToc,3509
29
30
  account/models/settings.py,sha256=gOyRWBVd3BQpjfj_hJPtqX3H46ztyRAFxBrPbv11lQg,2137
30
31
  account/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
32
  account/oauth/google.py,sha256=q5M6Qhpfp9QslKRVYFZBvtG6kgXV6vYMrR5fp6Xdb9I,2078
32
- account/periodic.py,sha256=imh9C-oQVhfw7GX3RRHYuC6DIreGbXrQM35A8C4UjOE,876
33
+ account/periodic.py,sha256=-u0n-7QTJgDOkasGhBAPwHAwjpqWGA-MZLEFkVTqCGU,874
33
34
  account/rpc/__init__.py,sha256=L_AqHC0WbgUgLoqvNz6pY0E34eqh7sRaX77I6TxhRZ4,152
34
- account/rpc/auth.py,sha256=8CgXsbNZLeBcd5MHpdL29z9E8PYUcKX_zYpTPX_Pa-0,12516
35
+ account/rpc/auth.py,sha256=TL74o-LH8ETqF0EzLb6Bis_lsLx9VEirA-ZvivBUPaE,13652
35
36
  account/rpc/device.py,sha256=fbbZFp3cUdhVXvD7gVFOqFWj4hKS3bjZKD_aF5fQxd8,2852
36
- account/rpc/group.py,sha256=dUIZGArPgnKafL7F44f7Qum5EAskWcjj64Haqlkbdb8,3448
37
+ account/rpc/group.py,sha256=Y_Ii-vlDx09neMd95AmC7xBwDf3wdFgXjB-kIG2jMdE,3472
37
38
  account/rpc/member.py,sha256=oKdXSGhQ7AOPTwisZ5RvHhQ1SdZoXWlBQY0lIlDXJY0,1150
38
39
  account/rpc/notify.py,sha256=Q2YWejP36egeF060Hih5uX4Psv_B8NWlLLPi7iDYlIw,3344
39
40
  account/rpc/oauth.py,sha256=-BW38HjYwSQhOs31ubnBxO0yCKVpczEMoHq54NC9uOU,2610
@@ -94,7 +95,7 @@ incident/migrations/0011_ticket.py,sha256=Ml5E_Qi4Z0MD89fetoOFOL3rPlVQdjaaDCcFBf
94
95
  incident/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
96
  incident/models/__init__.py,sha256=NMphuhb0RTMf7Ov4QkNv7iv6_I8Wtr3xQ54yjX_a31M,209
96
97
  incident/models/event.py,sha256=AXFV92OtnVi3opSZcRX1o3qZ8uBvqGFL549AXH2gjY8,6792
97
- incident/models/incident.py,sha256=qBwXC4lhQ8YZ7EatDjOced93cXq1jJqYgElgmKfl9nw,14409
98
+ incident/models/incident.py,sha256=zaq81_ZdGAeUDKpmP2c9Du8TPTMt2EW5e-Oyt8q2Lwo,14390
98
99
  incident/models/ossec.py,sha256=pWMqcuTRxPFTEF-OZQSMn7YpNEE9mfsI4GMhWWjJs5I,2187
99
100
  incident/models/rules.py,sha256=Ua-hC78_D6YikJjP3DXfA8ghi6zu__Qud8H1bk7MRG0,5342
100
101
  incident/models/ticket.py,sha256=S3kqGQpYLE6Y4M9IKu_60sgW-f592xNr8uufqHnvDoU,2302
@@ -350,7 +351,7 @@ pushit/utils.py,sha256=IeTCGa-164nmB1jIsK1lu1O1QzUhS3BKfuXHGjCW-ck,2121
350
351
  rest/.gitignore,sha256=TbEvWRMnAiajCTOdhiNrd9eeCAaIjRp9PRjE_VkMM5g,118
351
352
  rest/README.md,sha256=V3ETc-cJu8PZIbKr9xSe_pA4JEUpC8Dhw4bQeVCDJPw,5460
352
353
  rest/RemoteEvents.py,sha256=nL46U7AuxIrlw2JunphR1tsXyqi-ep_gD9CYGpYbNgE,72
353
- rest/__init__.py,sha256=EBklYrW3BC8Q1_xwHhntKj0NjkG0lEqi8Nw56aKm-K0,121
354
+ rest/__init__.py,sha256=42ez8LEkgPOOkm7pc1ftpMfoo52l5H5XggNBtlOWYYs,121
354
355
  rest/arc4.py,sha256=y644IbF1ec--e4cUJ3KEYsewTCITK0gmlwa5mJruFC0,1967
355
356
  rest/cache.py,sha256=1Qg0rkaCJCaVP0-l5hZg2CIblTdeBSlj_0fP6vlKUpU,83
356
357
  rest/crypto/__init__.py,sha256=Tl0U11rgj1eBYqd6OXJ2_XSdNLumW_JkBZnaJqI6Ldw,72
@@ -377,12 +378,12 @@ rest/management/commands/rpc.py,sha256=bQRisv4z34LyRR5xiS63glY5tgZC4SkYr7UWoCtHl
377
378
  rest/middleware/__init__.py,sha256=u4Z8Fl9uOuX2yHT_uISCLjLDofdGWrpaDNYrc6T1xE4,33
378
379
  rest/middleware/cors.py,sha256=sNAgZ3chnXI5WKYdcbTc8eXqxZLdM1tHns7lIoI5BPE,3513
379
380
  rest/middleware/db_router.py,sha256=jd0ywJK0PX6hz3Jy3DGRWfGr6v53tV-BtRIPm_32f0w,2345
380
- rest/middleware/jwt.py,sha256=jZ5_9hbSbegf7UYAdMYthEHaPCQw0WPf3kMymvQAd1E,5522
381
+ rest/middleware/jwt.py,sha256=dR8rWnb1BrJmlvCVz2I9d0GD2CfnbxutGlqItbZWs1E,6017
381
382
  rest/middleware/request.py,sha256=JchRNy5L-bGd-7h-KFYekGRvREe2eCkZXKOYqIkP2hI,4189
382
383
  rest/middleware/session.py,sha256=zHSoQpIzRLmpqr_JvW406wzpvU3W3gDbm5JhtzLAMlE,10240
383
384
  rest/middleware/session_store.py,sha256=X_i06TnZLW1srV0vkjjLhZ7fl1G56PswXxRpVzdFasw,1874
384
385
  rest/models/__init__.py,sha256=M8pvFDq-WCF-QcM58X7pMufYYe0aaQ3U0PwGe9TKbbY,130
385
- rest/models/base.py,sha256=Fo17FhaeZHIVfNP8M6eIoVVikxkOBAGGzsgNg8Fl7B8,65724
386
+ rest/models/base.py,sha256=pjilLSXpkNVdyOVm8ipExYpjW-P--w4yKW36j3-EOdY,65787
386
387
  rest/models/cacher.py,sha256=eKz8TINVhWEqKhJGMsRkKZTtBUIv5rN3NHbZwOC56Uk,578
387
388
  rest/models/metadata.py,sha256=VM5t3I1DYgvwLAOjaYcMdVZ2JWrrcJUMU3o3gATkPYk,12626
388
389
  rest/net.py,sha256=LTF4ip-ur8C2G7NETVOg7ioACegBGo4sDJA18PfF5kQ,1691
@@ -447,7 +448,7 @@ telephony/decorators.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
447
448
  telephony/migrations/0001_initial.py,sha256=YRiNtpeqj-4uvq19sfI64Q-KRnQ_T53kNzyt-CZUl14,3030
448
449
  telephony/migrations/0002_alter_sms_sid.py,sha256=QAnMG-UZ5emssZwdJ8XwfaRh3872zUUR55maDMD4RkE,424
449
450
  telephony/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
450
- telephony/models.py,sha256=S6k7rbG1GxO9nhffOOzbM-jgpm1PJEfO20mdj6IIpDI,8116
451
+ telephony/models.py,sha256=68BWPyTZ_bNptuCqhFEv3iraMp6bcWKXfJ7jnj_pUpo,8142
451
452
  telephony/phone_util.py,sha256=15IWkTSqkjDWT9edPAYogAxHc1OdjOziPSxsJ8vfpfE,2426
452
453
  telephony/rpc.py,sha256=GVPWH5XSPNar-ICu5oq-yF6FBmJbju3XJr6gSXA6ZVU,3535
453
454
  wiki/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
@@ -482,7 +483,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
482
483
  ws4redis/settings.py,sha256=K0yBiLUuY81iDM4Yr-k8hbvjn5VVHu5zQhmMK8Dtz0s,1536
483
484
  ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
484
485
  ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
485
- django_restit-4.1.75.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
486
- django_restit-4.1.75.dist-info/METADATA,sha256=hjRHe-VVVIQu1Sqhc6pz00FdyeLdszibBJ-5coivs7I,7573
487
- django_restit-4.1.75.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
488
- django_restit-4.1.75.dist-info/RECORD,,
486
+ django_restit-4.1.77.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
487
+ django_restit-4.1.77.dist-info/METADATA,sha256=AM9Kck1ngBJZB2d2puHI-_yNU9dFtFfjbbpr5onZm60,7573
488
+ django_restit-4.1.77.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
489
+ django_restit-4.1.77.dist-info/RECORD,,
@@ -168,8 +168,8 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
168
168
  action, perm = self.rule.action.split(":")
169
169
  members = Member.GetWithNotification(perm)
170
170
  # count = self.getProperty("event_count", default=1, field_type=int)
171
- url = F"{settings.INCIDENT_PORTAL_URL}"
172
- msg = f"Incident #{self.pk}\n{self.description}\n<a href='{url}?incident={self.pk}'>view</a>"
171
+ url = F"{settings.INCIDENT_PORTAL_URL}?incident={self.pk}"
172
+ msg = f"Incident #{self.pk}\n{self.description}\n{url}"
173
173
  for m in members:
174
174
  m.sendSMS(msg)
175
175
  except Exception:
rest/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from .uberdict import UberDict # noqa: F401
2
2
  from .settings_helper import settings # noqa: F401
3
3
 
4
- __version__ = "4.1.75"
4
+ __version__ = "4.1.77"
rest/middleware/jwt.py CHANGED
@@ -112,6 +112,18 @@ class JWTokenMiddleware(object):
112
112
  if atoken is None:
113
113
  helpers.log_error("login failed with authtoken")
114
114
  return
115
+ if atoken.member.requires_totp:
116
+ reason = F"auth failed bacause requires_totp prevents auth_token login for {atoken.member.username}"
117
+ atoken.member.log(
118
+ "login_blocked",
119
+ reason,
120
+ request, method="login", level=31)
121
+ atoken.member.reportIncident(
122
+ "account", reason, level=2,
123
+ error_code=444,
124
+ request=request,
125
+ details=reason)
126
+ return
115
127
  if not atoken.member.canLogin(request, False):
116
128
  helpers.log_error("user cannot login via authtoken", request.token)
117
129
  return
rest/models/base.py CHANGED
@@ -199,11 +199,13 @@ class RestModel(object):
199
199
 
200
200
  @classmethod
201
201
  def getGraph(cls, name):
202
- graph_key = "_graph_{0}__".format(name)
203
- if hasattr(cls, graph_key):
204
- return getattr(cls, graph_key)
202
+ if name is None:
203
+ name = "default"
204
+ graph_key = f"_graph_{name}__"
205
+ if hasattr(cls._meta, graph_key):
206
+ return getattr(cls._meta, graph_key)
205
207
  graph = cls.buildGraph(name)
206
- setattr(cls, graph_key, graph)
208
+ setattr(cls._meta, graph_key, graph)
207
209
  return graph
208
210
 
209
211
  def toGraph(self, request=None, graph="basic"):
telephony/models.py CHANGED
@@ -10,6 +10,7 @@ from . import phone_util
10
10
  class SMS(models.Model, RestModel):
11
11
  class RestMeta:
12
12
  CAN_CREATE = False
13
+ CAN_DELETE = True
13
14
  DEFAULT_SORT = "-created"
14
15
  QUERY_FIELDS = ["endpoint", "srcpoint"]
15
16
  SEARCH_FIELDS = ["endpoint", "srcpoint"]