django-restit 4.2.179__py3-none-any.whl → 4.2.180__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.
account/models/member.py CHANGED
@@ -357,7 +357,7 @@ class Member(User, RestModel, MetaDataModel):
357
357
  self.log("login_blocked", "password has expired", request, method="login", level=31)
358
358
  if throw_exception:
359
359
  raise PermissionDeniedException(
360
- "password expired", 412,
360
+ f"{self.username} password expired", 412,
361
361
  component="account.Member", component_id=self.id)
362
362
  return False
363
363
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-restit
3
- Version: 4.2.179
3
+ Version: 4.2.180
4
4
  Summary: A Rest Framework for DJANGO
5
5
  License: MIT
6
6
  Author: Ian Starnes
@@ -31,7 +31,7 @@ account/models/device.py,sha256=8D-Sbv9PZWAnX6UVpp1lNJ03P24fknNnN1VOhqY7RVg,6306
31
31
  account/models/feeds.py,sha256=vI7fG4ASY1M0Zjke24RdnfDcuWeATl_yR_25jPmT64g,2011
32
32
  account/models/group.py,sha256=Pmm6G5Qb9C-OQ70xUxp5M1_PAg1DtvUxb5WFFW-WmOY,22909
33
33
  account/models/legacy.py,sha256=zYdtv4LC0ooxPVqWM-uToPwV-lYWQLorSE6p6yn1xDw,2720
34
- account/models/member.py,sha256=xlX0yqE3yFwPaZkcdU4e3g3yTommHimsLG8wBp09x90,55376
34
+ account/models/member.py,sha256=xkVpADfnKkUEIg8EDy7OU077c0e5ZGEQKYvcwhGzXd0,55393
35
35
  account/models/membership.py,sha256=90EpAhOsGaqphDAkONP6j_qQ0OWSRaQsI8H7E7fgMkE,9249
36
36
  account/models/notify.py,sha256=YKYEXT56i98b7-ydLt5UuEVOqW7lipQMi-KuiPhcSwY,15627
37
37
  account/models/passkeys.py,sha256=lObapudvL--ABSTZTIELmYvHE3dPF0tO_KmuYk0ZJXc,1699
@@ -115,7 +115,7 @@ incident/migrations/0015_rule_title_template_alter_incident_state.py,sha256=FPUD
115
115
  incident/migrations/0016_rule_notify_template.py,sha256=4WGdMxiELujLIy9bzHovHWbAORupodN1Ty3vsy3mLjg,425
116
116
  incident/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
117
  incident/models/__init__.py,sha256=NMphuhb0RTMf7Ov4QkNv7iv6_I8Wtr3xQ54yjX_a31M,209
118
- incident/models/event.py,sha256=JEUdVUxqz2nRcfGC2GHo3baIg5JntK5cSL6_MesFLeA,7967
118
+ incident/models/event.py,sha256=skhnH4q80XkxcXNo1JNTAF1SzSLUssB3_mjtFX_xxw0,8864
119
119
  incident/models/incident.py,sha256=oxwLDJYGmk26zf7AD_e7nKcoJw3gXibjrX3DlByNAT8,22580
120
120
  incident/models/ossec.py,sha256=g7cc2vYdYEB8zomohwqbo0ekyPt1v_qA67y35sBn2YY,2244
121
121
  incident/models/rules.py,sha256=PPp8oJDW1gop9i_21lhP50qgt_TrdWErp2mYqZCMfd4,7065
@@ -380,7 +380,7 @@ pushit/utils.py,sha256=IeTCGa-164nmB1jIsK1lu1O1QzUhS3BKfuXHGjCW-ck,2121
380
380
  rest/.gitignore,sha256=TbEvWRMnAiajCTOdhiNrd9eeCAaIjRp9PRjE_VkMM5g,118
381
381
  rest/README.md,sha256=V3ETc-cJu8PZIbKr9xSe_pA4JEUpC8Dhw4bQeVCDJPw,5460
382
382
  rest/RemoteEvents.py,sha256=nL46U7AuxIrlw2JunphR1tsXyqi-ep_gD9CYGpYbNgE,72
383
- rest/__init__.py,sha256=wfYI2fFctsIeYnQP9m01ZTnkw31NpQQDKrhgfS_4Kgw,122
383
+ rest/__init__.py,sha256=01ohdi9tNjAEK-JvD5T6Orp2UIBd3QpmjM5PeLM8DU4,122
384
384
  rest/arc4.py,sha256=y644IbF1ec--e4cUJ3KEYsewTCITK0gmlwa5mJruFC0,1967
385
385
  rest/cache.py,sha256=1Qg0rkaCJCaVP0-l5hZg2CIblTdeBSlj_0fP6vlKUpU,83
386
386
  rest/crypto/__init__.py,sha256=Tl0U11rgj1eBYqd6OXJ2_XSdNLumW_JkBZnaJqI6Ldw,72
@@ -388,7 +388,7 @@ rest/crypto/aes.py,sha256=NOVRBRSHCV-om68YpGySWWG-4kako3iEVjq8hxZWPUU,4372
388
388
  rest/crypto/privpub.py,sha256=_FioylVcbMmDP80yPYjURmafEiDmEAMkskbc7WF10ac,4082
389
389
  rest/crypto/util.py,sha256=agFN2OCPHC70tHNGWrMkkZX4Tt_Ty6imoKEMdTkZpKA,4514
390
390
  rest/datem.py,sha256=qbkgDpTVbxpnS4hRm_GnyRo9Gk7nRJbH-tJqxm172mo,13152
391
- rest/decorators.py,sha256=AuB4agpog587CUsF8HkAZiHDfs_pueb2rdxXZD7dUUE,15327
391
+ rest/decorators.py,sha256=1lLbjxSHbGgJlFkDQ3QqOPaLpBRZK0dw2OMrLnAT9XQ,15382
392
392
  rest/encryption.py,sha256=x6Kiez0tVqfxK26MSsRL3k8OS05ni1gEX2aj3I0S9V0,788
393
393
  rest/errors.py,sha256=uKwG9OkLme36etabqK54DMjMQc1fgEoUIAUxXa7WFQw,612
394
394
  rest/extra/__init__.py,sha256=YzmNsch5H5FFLkUK9mIAKyoRK_rJCA9HGb0kubp4h30,54
@@ -494,11 +494,11 @@ wiki/models/faq.py,sha256=nvcEFerllQKT61kIYlasvZzRKwpXyfmQpiqkpHP1V1o,1745
494
494
  wiki/models/page.py,sha256=uIu-jVdf1Y9qXKbb5k15-8RLTltyTtoXobZJFcx8_0A,8836
495
495
  wiki/models/revision.py,sha256=St5-vz8SGvogsDL6jTWqHLE23PS5mp9iA0DUt3hWTsU,729
496
496
  wiki/periodic.py,sha256=t-UgXJIug-OLslJM_r03-5WrNKj39TxrvfuNFjVAhDs,334
497
- wiki/renderers/__init__.py,sha256=lLEoJvjU3ezXwBGcjleKk_kMyNeMD9MpfBlEiKayEiM,461
497
+ wiki/renderers/__init__.py,sha256=si9LdyUtGWbazsddEt7HY9Fl3iJYAeI_Hu9qf1J154I,682
498
498
  wiki/renderers/mistune/__init__.py,sha256=baClLWELOwy5n8UUFi7qmoFBU6QaeegD-wRNZ7fIW_w,84
499
499
  wiki/renderers/mistune/highlight.py,sha256=BosglMQUxc_KVbLapQ4gCt6rCc0rAF1vCtUR7R1Ad4c,1264
500
500
  wiki/renderers/mistune/math.py,sha256=dgQpH9CIDiqyESphoK5XUVFxK5Yb5VhEIoLgjp3Vtcs,1901
501
- wiki/renderers/mistune/media.py,sha256=SYwjhX6_DKfqJ00yLxfKrIHu6JnnwKnroqRm6ySDMfY,2688
501
+ wiki/renderers/mistune/media.py,sha256=wV7VYfKxhlfhxPAmpL28COXvhRQ1eQulPgV6hBYACF8,2747
502
502
  wiki/renderers/mistune/meta.py,sha256=1lry9m-4wiwsivWnqYHYjwmGv91BGUSB7niJuZ1Xx54,805
503
503
  wiki/renderers/mistune/task_list.py,sha256=Ex0gUPX_d9jtbPbnEEktlqAJsM6wIt5Md2wsltX7LIY,1889
504
504
  wiki/renderers/mistune/toc.py,sha256=TKGiuMVpKqzDGUx5bAjJYpZIzG6n3wTjtuBdBc-TM_8,2302
@@ -518,7 +518,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
518
518
  ws4redis/settings.py,sha256=KKq00EwoGnz1yLwCZr5Dfoq2izivmAdsNEEM4EhZwN4,1610
519
519
  ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
520
520
  ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
521
- django_restit-4.2.179.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
522
- django_restit-4.2.179.dist-info/METADATA,sha256=Pmxj3TIYbIsFJEZzboR6iygMixbAjdnCGvBbqg2eTLA,7714
523
- django_restit-4.2.179.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
524
- django_restit-4.2.179.dist-info/RECORD,,
521
+ django_restit-4.2.180.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
522
+ django_restit-4.2.180.dist-info/METADATA,sha256=PtXqvmNGrzGdzNKLZvdYKbY_oKYMWZQ54JtWVWk03z8,7714
523
+ django_restit-4.2.180.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
524
+ django_restit-4.2.180.dist-info/RECORD,,
incident/models/event.py CHANGED
@@ -129,85 +129,135 @@ class Event(JSONMetaData, rm.RestModel):
129
129
  value = value[:80] + "..."
130
130
  self.description = value
131
131
 
132
- def on_rest_pre_save(self, request):
132
+
133
+ def on_rest_created(self, request):
134
+ # Record metrics if enabled
135
+ if INCIDENT_EVENT_METRICS:
136
+ self._record_event_metrics()
137
+
138
+ self._update_properties(request)
139
+ # Process rules and create incident if needed
140
+ hit_rule = self.runRules()
141
+ incident = self._process_rules_and_create_incident(hit_rule)
142
+
143
+ if incident is None:
144
+ # No incident needed based on rules
145
+ return
146
+
147
+ # Update incident metadata
148
+ self.incident = incident
149
+ self.save()
150
+
151
+ # Record incident metrics if enabled
152
+ if INCIDENT_METRICS:
153
+ self._record_incident_metrics()
154
+
155
+ # Trigger any incident actions
156
+ try:
157
+ incident.triggerAction()
158
+ except Exception:
159
+ logger.exception()
160
+
161
+ def _update_properties(self, request):
162
+ # make sure hostname is set
133
163
  if self.hostname is None:
134
164
  self.hostname = settings.HOSTNAME
165
+
166
+ # Update properties
167
+ self.setProperty("level", self.level)
168
+ self.setProperty("category", self.category)
135
169
  if not self.getProperty("hostname", None):
136
170
  self.setProperty("hostname", self.hostname)
137
- self.setProperty("category", self.category)
171
+
172
+ # lookup IP
138
173
  if request and request.DATA.get("ip_lookup", field_type=bool):
139
174
  self.reporter_ip = request.ip
140
175
  self.lookupIP(request.ip)
141
176
 
142
- def on_rest_saved(self, request, is_new=False):
143
- if not is_new:
144
- return
145
- if INCIDENT_EVENT_METRICS:
146
- if self.hostname:
147
- metrics.metric(f"incident_evt_{self.hostname}", category="incident_events", min_granularity="hourly")
148
- metrics.metric("incident_evt", min_granularity=INCIDENT_EVENT_GRANULARITY)
177
+ def _record_event_metrics(self):
178
+ """Record event metrics"""
179
+ if self.hostname:
180
+ metrics.metric(
181
+ f"incident_evt_{self.hostname}",
182
+ category="incident_events",
183
+ min_granularity="hourly"
184
+ )
185
+ metrics.metric(
186
+ "incident_evt",
187
+ min_granularity=INCIDENT_EVENT_GRANULARITY
188
+ )
149
189
 
150
- self.setProperty("level", self.level)
151
- if request is not None:
152
- self.reporter_ip = request.ip
153
- # run through rules for the category
154
- hit_rule = self.runRules()
155
- priority = 10
156
- incident = None
157
- action_count = 0
190
+ def _record_incident_metrics(self):
191
+ """Record incident metrics"""
192
+ if self.hostname:
193
+ metrics.metric(
194
+ f"incidents_{self.hostname}",
195
+ category="incidents",
196
+ min_granularity="hourly"
197
+ )
198
+ metrics.metric(
199
+ "incidents",
200
+ min_granularity=INCIDENT_EVENT_GRANULARITY
201
+ )
202
+
203
+ def _process_rules_and_create_incident(self, hit_rule):
204
+ """Process rules and create incident if needed"""
158
205
  if hit_rule is not None:
159
- # logger.error(f"RULE HIT: {hit_rule.name}")
160
- priority = hit_rule.priority
206
+ # Handle rule matches
161
207
  if hit_rule.action == "ignore":
162
208
  self.save()
163
- return
209
+ return None
210
+
164
211
  if hit_rule.bundle > 0:
165
- incident = Incident.getBundled(rule=hit_rule, event=self)
212
+ return Incident.getBundled(rule=hit_rule, event=self)
166
213
 
167
214
  elif self.level >= EVENT_TO_INCIDENT_LEVEL:
168
- # we ignore levels 4 and higher if they did not create a rule
215
+ # Ignore high levels without rules
169
216
  self.save()
170
- # logger.info(f"ignore event {self.pk} {self.description}")
171
- return
217
+ return None
172
218
 
173
- # always create an incident
174
- if incident is None:
175
- incident = Incident(
176
- rule=hit_rule, priority=priority,
177
- reporter_ip=self.reporter_ip,
178
- category=self.category,
179
- group=self.group,
180
- component=self.component,
181
- component_id=self.component_id,
182
- hostname=self.hostname)
183
- if self.group is None and hit_rule is not None:
184
- incident.group = hit_rule.group
185
- if hit_rule is not None and hit_rule.action_after != 0:
186
- incident.state = INCIDENT_STATE_PENDING
187
- # TODO possibly make this smarter?
188
- if hit_rule and hit_rule.title_template and "{" in hit_rule.title_template:
189
- try:
190
- incident.description = hit_rule.title_template.format(event=self)
191
- except Exception:
192
- logger.exception(hit_rule.title_template)
193
- incident.description = self.description
194
- elif self.category == "ossec":
195
- incident.description = f"{self.hostname}: {self.description}"
196
- incident.save()
197
- incident.updateAbuseInfo()
198
- else:
219
+ # Create new incident
220
+ return self._create_incident(hit_rule)
221
+
222
+ def _create_incident(self, hit_rule):
223
+ """Create a new incident"""
224
+ incident = Incident(
225
+ rule=hit_rule,
226
+ priority=hit_rule.priority if hit_rule else 10,
227
+ reporter_ip=self.reporter_ip,
228
+ category=self.category,
229
+ group=self.group,
230
+ component=self.component,
231
+ component_id=self.component_id,
232
+ hostname=self.hostname
233
+ )
234
+
235
+ # Set incident group from rule if needed
236
+ if self.group is None and hit_rule is not None:
237
+ incident.group = hit_rule.group
238
+
239
+ # Set pending state if rule requires
240
+ if hit_rule is not None and hit_rule.action_after != 0:
241
+ incident.state = INCIDENT_STATE_PENDING
242
+
243
+ # Set description based on rules and category
244
+ self._set_incident_description(incident, hit_rule)
245
+
246
+ incident.save()
247
+ incident.updateMeta(self)
248
+ return incident
249
+
250
+ def _set_incident_description(self, incident, hit_rule):
251
+ """Set the incident description"""
252
+ if hit_rule and hit_rule.title_template and "{" in hit_rule.title_template:
253
+ try:
254
+ incident.description = hit_rule.title_template.format(event=self)
255
+ except Exception:
256
+ logger.exception(hit_rule.title_template)
199
257
  incident.description = self.description
258
+ elif self.category == "ossec":
259
+ incident.description = f"{self.hostname}: {self.description}"
200
260
  incident.save()
201
- incident.updateMeta(self)
202
- self.incident = incident
203
- self.save()
204
- if INCIDENT_METRICS:
205
- # we want to track any incidents, including bundles
206
- if self.hostname:
207
- metrics.metric(f"incidents_{self.hostname}", category="incidents", min_granularity="hourly")
208
- metrics.metric("incidents", min_granularity=INCIDENT_EVENT_GRANULARITY)
209
-
210
- try:
211
- incident.triggerAction()
212
- except Exception:
213
- logger.exception()
261
+ incident.updateAbuseInfo()
262
+ else:
263
+ incident.description = self.description
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.2.179"
4
+ __version__ = "4.2.180"
rest/decorators.py CHANGED
@@ -62,7 +62,7 @@ def rest_error_catcher(func, request, *args, **kwargs):
62
62
  metrics.metric(f"rest_call_{slug_path}", category="rest_calls", min_granularity=REST_METRICS_GRANULARITY)
63
63
  return func(request, *args, **kwargs)
64
64
  except PermissionDeniedException as err:
65
- return rv.restPermissionDenied(request, err.reason, err.code)
65
+ return rv.restPermissionDenied(request, err.reason, err.code, component=err.component, component_id=err.component_id)
66
66
  except RestError as err:
67
67
  rh.log_exception("REST ERROR", request.path, err.reason)
68
68
  if settings.get("REST_ERROR_METRICS", True):
@@ -437,4 +437,3 @@ def periodic(minute=None, hour=None, day=None, month=None, weekday=None, tz=None
437
437
  inner_func.is_periodic = True
438
438
  return inner_func
439
439
  return decorator
440
-
@@ -2,14 +2,23 @@ from .mistune.highlight import HighlightMixin
2
2
  from .mistune.toc import TocMixin
3
3
  from .mistune.media import MediaMixin
4
4
  import mistune
5
+ import re
6
+
5
7
  try:
6
8
  HTMLRenderer = mistune.HTMLRenderer
7
9
  except Exception:
8
10
  HTMLRenderer = mistune.Renderer
9
11
 
10
12
 
13
+ def slugify(text):
14
+ return re.sub(r'\W+', '-', text.strip().lower()).strip('-')
15
+
11
16
  class WikiRenderer(TocMixin, HighlightMixin, MediaMixin, HTMLRenderer):
12
17
  def __init__(self, *args, **kwargs):
13
18
  # self.enable_math()
14
19
  self.reset_toc()
15
20
  super(WikiRenderer, self).__init__(*args, **kwargs)
21
+
22
+ def heading(self, text, level):
23
+ slug = slugify(text)
24
+ return f'<h{level} id="{slug}">{text}</h{level}>\n'
@@ -48,10 +48,11 @@ class MediaMixin(object):
48
48
  params = {"href":href}
49
49
  if title:
50
50
  params["title"] = title
51
- if label:
52
- params["download"] = label
53
- if not href.startswith("http"):
51
+
52
+ if not href.startswith("http") and not href.startswith("#"):
54
53
  params["data-action"] = "local_page"
54
+ elif label and href.startswith("http"):
55
+ params["download"] = label
55
56
  flat_params = ' '.join("{}='{}'".format(key,val) for (key,val) in list(params.items()))
56
57
  return """<a {}>{}</a>""".format(flat_params, (text or link))
57
58
 
@@ -87,4 +88,3 @@ class MediaMixin(object):
87
88
 
88
89
  flat_params = ' '.join("{}='{}'".format(key,val) for (key,val) in list(params.items()))
89
90
  return '<img {} />'.format(flat_params)
90
-