django-log-formatter-asim 1.1.0a1__tar.gz → 1.1.0a3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-log-formatter-asim
3
- Version: 1.1.0a1
3
+ Version: 1.1.0a3
4
4
  Summary: Formats Django logs in ASIM format.
5
5
  License: MIT
6
6
  Author: Department for Business and Trade Platform Team
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Requires-Dist: ddtrace (>=3.2.1,<4.0.0)
17
17
  Requires-Dist: django (>=3,<5) ; python_version == "3.9"
18
18
  Requires-Dist: django (>=3,<6) ; python_version >= "3.10" and python_version < "4"
19
+ Requires-Dist: django-ipware (>=7.0.1,<8.0.0)
19
20
  Description-Content-Type: text/markdown
20
21
 
21
22
  # Django ASIM log formatter
@@ -1,8 +1,9 @@
1
1
  import json
2
2
  import logging
3
+ import os
3
4
  from datetime import datetime
5
+ from datetime import timezone
4
6
  from importlib.metadata import distribution
5
- import os
6
7
 
7
8
  import ddtrace
8
9
  from ddtrace.trace import tracer
@@ -70,7 +71,7 @@ class ASIMRootFormatter:
70
71
 
71
72
  def get_log_dict(self):
72
73
  record = self.record
73
- log_time = datetime.utcfromtimestamp(record.created).isoformat()
74
+ log_time = datetime.fromtimestamp(record.created, timezone.utc).isoformat()
74
75
  log_dict = {
75
76
  # Event fields...
76
77
  "EventMessage": record.getMessage(),
@@ -200,14 +201,6 @@ class ASIMRequestFormatter(ASIMRootFormatter):
200
201
  http_user_agent = request.META.get("HTTP_USER_AGENT", None)
201
202
  return http_user_agent
202
203
 
203
- def _get_ip_address(self, request):
204
- # Import here as ipware uses settings
205
- from ipware import get_client_ip
206
-
207
- client_ip, is_routable = get_client_ip(request)
208
- return client_ip or "Unknown"
209
-
210
-
211
204
  ASIM_FORMATTERS = {
212
205
  "root": ASIMRootFormatter,
213
206
  "django.request": ASIMRequestFormatter,
@@ -12,6 +12,7 @@ from .common import Result
12
12
  from .common import Server
13
13
  from .common import Severity
14
14
  from .common import _default_severity
15
+ from .common import _get_client_ip_address
15
16
 
16
17
 
17
18
  class AuthenticationEvent(str, Enum):
@@ -50,6 +51,12 @@ class AuthenticationUser(TypedDict):
50
51
  """
51
52
  username: Optional[str]
52
53
  """
54
+ Email address for the user if one exists.
55
+
56
+ Defaults to the logged in Django User.email if not provided.
57
+ """
58
+ email: Optional[str]
59
+ """
53
60
  A unique identifier for this authentication session if one exists.
54
61
 
55
62
  Defaults to the Django Sessions session key if not provided.
@@ -78,6 +85,7 @@ def log_authentication(
78
85
  - Django Authentication systems current username
79
86
  - Django Session middlewares Session Key
80
87
  - Client IP address
88
+ - URL requested by the client
81
89
  - Server hostname
82
90
  :param event: What authentication action was attempted, either "Logon" or "Logoff"
83
91
  :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
@@ -126,13 +134,18 @@ def log_authentication(
126
134
 
127
135
  if "hostname" in server:
128
136
  log["DvcHostname"] = server["hostname"]
129
- elif hasattr(request, "environ") and "SERVER_NAME" in request.environ:
130
- log["DvcHostname"] = request.environ["SERVER_NAME"]
137
+ elif "HTTP_HOST" in request.META:
138
+ log["DvcHostname"] = request.get_host()
131
139
 
132
140
  if "ip_address" in client:
133
141
  log["SrcIpAddr"] = client["ip_address"]
134
- elif hasattr(request, "environ") and "REMOTE_ADDR" in request.environ:
135
- log["SrcIpAddr"] = request.environ.get("REMOTE_ADDR")
142
+ elif client_ip := _get_client_ip_address(request):
143
+ log["SrcIpAddr"] = client_ip
144
+
145
+ if "requested_url" in client:
146
+ log["TargetUrl"] = client["requested_url"]
147
+ elif "HTTP_HOST" in request.META:
148
+ log["TargetUrl"] = request.scheme + "://" + request.get_host() + request.get_full_path()
136
149
 
137
150
  if "role" in user:
138
151
  log["ActorUserType"] = user["role"]
@@ -143,9 +156,14 @@ def log_authentication(
143
156
  log["ActorSessionId"] = request.session.session_key
144
157
 
145
158
  if "username" in user:
146
- log["ActorUsername"] = user["username"]
147
- elif request.user.username:
148
- log["ActorUsername"] = request.user.username
159
+ log["TargetUsername"] = user["username"]
160
+ elif hasattr(request, "user") and request.user.username:
161
+ log["TargetUsername"] = request.user.username
162
+
163
+ if "email" in user:
164
+ log["ActorUsername"] = user["email"]
165
+ elif hasattr(request, "user") and hasattr(request.user, "email") and request.user.email:
166
+ log["ActorUsername"] = request.user.email
149
167
 
150
168
  if result_details:
151
169
  log["EventResultDetails"] = result_details
@@ -2,6 +2,8 @@ from enum import Enum
2
2
  from typing import Optional
3
3
  from typing import TypedDict
4
4
 
5
+ from django.http import HttpRequest
6
+
5
7
 
6
8
  class Result(str, Enum):
7
9
  Success = "Success"
@@ -23,6 +25,8 @@ class Client(TypedDict):
23
25
  """Internet Protocol Address of the client making the Authentication
24
26
  event."""
25
27
  ip_address: Optional[str]
28
+ """URL requested by the client."""
29
+ requested_url: Optional[str]
26
30
 
27
31
 
28
32
  class Server(TypedDict):
@@ -31,7 +35,7 @@ class Server(TypedDict):
31
35
  """
32
36
  A unique identifier for the server which serviced the Authentication event.
33
37
 
34
- Defaults to the WSGI SERVER_NAME field if not provided.
38
+ Defaults to the WSGI HTTP_HOST field if not provided.
35
39
  """
36
40
  hostname: Optional[str]
37
41
  """Internet Protocol Address of the server serving this request."""
@@ -40,3 +44,11 @@ class Server(TypedDict):
40
44
 
41
45
  def _default_severity(result: Result) -> Severity:
42
46
  return Severity.Informational if result == Result.Success else Severity.Medium
47
+
48
+
49
+ def _get_client_ip_address(request: HttpRequest) -> Optional[str]:
50
+ # Import here as ipware uses settings
51
+ from ipware import get_client_ip
52
+
53
+ client_ip, _ = get_client_ip(request)
54
+ return client_ip
@@ -12,6 +12,7 @@ from .common import Result
12
12
  from .common import Server
13
13
  from .common import Severity
14
14
  from .common import _default_severity
15
+ from .common import _get_client_ip_address
15
16
 
16
17
 
17
18
  class FileActivityEvent(str, Enum):
@@ -92,6 +93,7 @@ def log_file_activity(
92
93
  from which the following data will be logged if available
93
94
  - Django Authentication systems current username
94
95
  - Client IP address
96
+ - URL requested by the client
95
97
  - Server hostname
96
98
  :param event: What File Event action was attempted, one of:
97
99
  - FileAccessed
@@ -167,18 +169,23 @@ def log_file_activity(
167
169
 
168
170
  if "hostname" in server:
169
171
  log["DvcHostname"] = server["hostname"]
170
- elif hasattr(request, "environ") and "SERVER_NAME" in request.environ:
171
- log["DvcHostname"] = request.environ["SERVER_NAME"]
172
+ elif "HTTP_HOST" in request.META:
173
+ log["DvcHostname"] = request.get_host()
172
174
 
173
175
  if "ip_address" in client:
174
176
  log["SrcIpAddr"] = client["ip_address"]
175
- elif hasattr(request, "environ") and "REMOTE_ADDR" in request.environ:
176
- log["SrcIpAddr"] = request.environ.get("REMOTE_ADDR")
177
+ elif client_ip := _get_client_ip_address(request):
178
+ log["SrcIpAddr"] = client_ip
179
+
180
+ if "requested_url" in client:
181
+ log["TargetUrl"] = client["requested_url"]
182
+ elif "HTTP_HOST" in request.META:
183
+ log["TargetUrl"] = request.scheme + "://" + request.get_host() + request.get_full_path()
177
184
 
178
185
  if "username" in user:
179
- log["ActorUsername"] = user["username"]
186
+ log["TargetUsername"] = user["username"]
180
187
  elif request.user.username:
181
- log["ActorUsername"] = request.user.username
188
+ log["TargetUsername"] = request.user.username
182
189
 
183
190
  if result_details:
184
191
  log["EventResultDetails"] = result_details
@@ -3,7 +3,7 @@ line-length = 100
3
3
 
4
4
  [tool.poetry]
5
5
  name = "django-log-formatter-asim"
6
- version = "1.1.0a1"
6
+ version = "1.1.0a3"
7
7
  description = "Formats Django logs in ASIM format."
8
8
  authors = ["Department for Business and Trade Platform Team <sre-team@digital.trade.gov.uk>"]
9
9
  license = "MIT"
@@ -19,12 +19,13 @@ django = [
19
19
  { version = ">=3,<6", python = ">=3.10,<4" },
20
20
  ]
21
21
  ddtrace = "^3.2.1"
22
+ django-ipware = "^7.0.1"
22
23
 
23
24
  [tool.poetry.group.dev.dependencies]
24
25
  pre-commit = "^3.5.0"
25
26
  pytest = "^7.4.3"
26
27
  tox = "^4.11.3"
27
- freezegun = "^1.2.2"
28
+ freezegun = "^1.5.2"
28
29
  pytest-django = "^4.7.0"
29
30
  importlib-metadata = "^6.8.0"
30
31