django-log-formatter-asim 1.0.0__py3-none-any.whl → 1.1.0a1__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,2 @@
1
+ from .authentication import log_authentication
2
+ from .file_activity import log_file_activity
@@ -0,0 +1,179 @@
1
+ import datetime
2
+ import json
3
+ from enum import Enum
4
+ from typing import Literal
5
+ from typing import Optional
6
+ from typing import TypedDict
7
+
8
+ from django.http import HttpRequest
9
+
10
+ from .common import Client
11
+ from .common import Result
12
+ from .common import Server
13
+ from .common import Severity
14
+ from .common import _default_severity
15
+
16
+
17
+ class AuthenticationEvent(str, Enum):
18
+ Logon = "Logon"
19
+ Logoff = "Logoff"
20
+
21
+
22
+ class AuthenticationLoginMethod(str, Enum):
23
+ UsernamePassword = "Username & Password"
24
+ StaffSSO = "Staff-SSO"
25
+ UKGOVSSO = "UK.GOV-SSO"
26
+ ExternalIDP = "External IdP"
27
+
28
+
29
+ class AuthenticationUser(TypedDict):
30
+ """Dictionary to represent properties of the users session."""
31
+
32
+ """What type of role best describes this Authentication event."""
33
+ role: Optional[
34
+ Literal[
35
+ "Regular",
36
+ "Machine",
37
+ "Admin",
38
+ "System",
39
+ "Application",
40
+ "Service Principal",
41
+ "Service",
42
+ "Anonymous",
43
+ "Other",
44
+ ]
45
+ ]
46
+ """
47
+ A unique identifier for the user.
48
+
49
+ Defaults to the logged in Django User.username if not provided.
50
+ """
51
+ username: Optional[str]
52
+ """
53
+ A unique identifier for this authentication session if one exists.
54
+
55
+ Defaults to the Django Sessions session key if not provided.
56
+ """
57
+ sessionId: Optional[str]
58
+
59
+
60
+ def log_authentication(
61
+ request: HttpRequest,
62
+ event: AuthenticationEvent,
63
+ result: Result,
64
+ login_method: AuthenticationLoginMethod,
65
+ user: Optional[AuthenticationUser] = None,
66
+ server: Optional[Server] = None,
67
+ client: Optional[Client] = None,
68
+ severity: Optional[Severity] = None,
69
+ time_generated: Optional[datetime.datetime] = None,
70
+ result_details: Optional[str] = None,
71
+ message: Optional[str] = None,
72
+ ):
73
+ """
74
+ Log an ASIM Authentication Event to standard output.
75
+
76
+ :param request: django.http.HttpRequest object which initiated this Authentication request
77
+ from which the following data will be logged if available
78
+ - Django Authentication systems current username
79
+ - Django Session middlewares Session Key
80
+ - Client IP address
81
+ - Server hostname
82
+ :param event: What authentication action was attempted, either "Logon" or "Logoff"
83
+ :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
84
+ :param login_method: What authentication mechanism was being used, one of:
85
+ - "Username & Password"
86
+ - "Staff-SSO"
87
+ - "UK.GOV-SSO"
88
+ - "External IdP"
89
+ :param user: Dictionary containing information on the subject of this Authentication event
90
+ see AuthenticationUser class for more details.
91
+ :param server: Dictionary containing information on the server servicing this Authentication event
92
+ see Server class for more details.
93
+ :param client: Dictionary containing information on the client performing this Authentication event
94
+ see Client class for more details.
95
+ :param severity: Optional severity of the event, defaults to "Informational", otherwise one of:
96
+ - "Informational"
97
+ - "Low"
98
+ - "Medium"
99
+ - "High"
100
+ :param time_generated: Optional datetime for when the event happened, otherwise datetime.now
101
+ :param result_details: Optional string describing any details associated with the events outcome.
102
+ This field is typically populated when the result is a failure.
103
+ :param message: Optional string describing the reason why the log was generated.
104
+
105
+ See also: https://learn.microsoft.com/en-us/azure/sentinel/normalization-schema-authentication
106
+ """
107
+ if user == None:
108
+ user = {}
109
+ if server == None:
110
+ server = {}
111
+ if client == None:
112
+ client = {}
113
+
114
+ event_created = time_generated or datetime.datetime.now(tz=datetime.timezone.utc)
115
+
116
+ log = {
117
+ "EventCreated": event_created.isoformat(), # TODO: Should this really be EventCreated, or TimeGenerated
118
+ "EventSeverity": severity or _default_severity(result),
119
+ "EventOriginalType": _event_code(event, result),
120
+ "EventType": event,
121
+ "EventResult": result,
122
+ "LogonMethod": login_method,
123
+ "EventSchema": "Authentication",
124
+ "EventSchemaVersion": "0.1.4",
125
+ }
126
+
127
+ if "hostname" in server:
128
+ log["DvcHostname"] = server["hostname"]
129
+ elif hasattr(request, "environ") and "SERVER_NAME" in request.environ:
130
+ log["DvcHostname"] = request.environ["SERVER_NAME"]
131
+
132
+ if "ip_address" in client:
133
+ log["SrcIpAddr"] = client["ip_address"]
134
+ elif hasattr(request, "environ") and "REMOTE_ADDR" in request.environ:
135
+ log["SrcIpAddr"] = request.environ.get("REMOTE_ADDR")
136
+
137
+ if "role" in user:
138
+ log["ActorUserType"] = user["role"]
139
+
140
+ if "sessionId" in user:
141
+ log["ActorSessionId"] = user["sessionId"]
142
+ elif request.session.session_key:
143
+ log["ActorSessionId"] = request.session.session_key
144
+
145
+ if "username" in user:
146
+ log["ActorUsername"] = user["username"]
147
+ elif request.user.username:
148
+ log["ActorUsername"] = request.user.username
149
+
150
+ if result_details:
151
+ log["EventResultDetails"] = result_details
152
+
153
+ if message:
154
+ log["EventMessage"] = message
155
+
156
+ if "ip_address" in server:
157
+ log["DvcIpAddr"] = server["ip_address"]
158
+
159
+ print(json.dumps(log), flush=True)
160
+
161
+
162
+ log_authentication.Event = AuthenticationEvent
163
+ log_authentication.Result = Result
164
+ log_authentication.LoginMethod = AuthenticationLoginMethod
165
+ log_authentication.Severity = Severity
166
+
167
+
168
+ def _event_code(event: AuthenticationEvent, result: Result) -> str:
169
+ if event == AuthenticationEvent.Logon:
170
+ if result == Result.Success:
171
+ return "001a"
172
+ elif result == Result.Failure:
173
+ return "001b"
174
+ elif event == AuthenticationEvent.Logoff:
175
+ if result == Result.Success:
176
+ return "001c"
177
+ elif result == Result.Failure:
178
+ return "001d"
179
+ return "001"
@@ -0,0 +1,42 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+ from typing import TypedDict
4
+
5
+
6
+ class Result(str, Enum):
7
+ Success = "Success"
8
+ Partial = "Partial"
9
+ Failure = "Failure"
10
+ NA = "NA"
11
+
12
+
13
+ class Severity(str, Enum):
14
+ Informational = "Informational"
15
+ Low = "Low"
16
+ Medium = "Medium"
17
+ High = "High"
18
+
19
+
20
+ class Client(TypedDict):
21
+ """Dictionary to represent properties of the HTTP Client."""
22
+
23
+ """Internet Protocol Address of the client making the Authentication
24
+ event."""
25
+ ip_address: Optional[str]
26
+
27
+
28
+ class Server(TypedDict):
29
+ """Dictionary to represent properties of the HTTP Server."""
30
+
31
+ """
32
+ A unique identifier for the server which serviced the Authentication event.
33
+
34
+ Defaults to the WSGI SERVER_NAME field if not provided.
35
+ """
36
+ hostname: Optional[str]
37
+ """Internet Protocol Address of the server serving this request."""
38
+ ip_address: Optional[str]
39
+
40
+
41
+ def _default_severity(result: Result) -> Severity:
42
+ return Severity.Informational if result == Result.Success else Severity.Medium
@@ -0,0 +1,197 @@
1
+ import datetime
2
+ import json
3
+ import os
4
+ from enum import Enum
5
+ from typing import Optional
6
+ from typing import TypedDict
7
+
8
+ from django.http import HttpRequest
9
+
10
+ from .common import Client
11
+ from .common import Result
12
+ from .common import Server
13
+ from .common import Severity
14
+ from .common import _default_severity
15
+
16
+
17
+ class FileActivityEvent(str, Enum):
18
+ FileAccessed = "FileAccessed"
19
+ FileCreated = "FileCreated"
20
+ FileModified = "FileModified"
21
+ FileDeleted = "FileDeleted"
22
+ FileRenamed = "FileRenamed"
23
+ FileCopied = "FileCopied"
24
+ FileMoved = "FileMoved"
25
+ FolderCreated = "FolderCreated"
26
+ FolderDeleted = "FolderDeleted"
27
+ FolderMoved = "FolderMoved"
28
+ FolderModified = "FolderModified"
29
+
30
+
31
+ class FileActivityFile(TypedDict):
32
+ """Dictionary to represent properties of the target file."""
33
+
34
+ """
35
+ The full, normalized path of the target file, including the folder or location,
36
+ the file name, and the extension.
37
+ """
38
+ path: str
39
+ """
40
+ The name of the target file, without a path or a location, but with an
41
+ extension if available. This field should be similar to the final element in
42
+ the TargetFilePath field.
43
+
44
+ Defaults to extracting the name based off the path if not provided.
45
+ """
46
+ name: Optional[str]
47
+ """
48
+ The target file extension.
49
+
50
+ Defaults to extracting the extension based off the path if not provided.
51
+ """
52
+ extension: Optional[str]
53
+ """
54
+ The Mime, or Media, type of the target file.
55
+
56
+ Allowed values are listed in the IANA Media Types repository.
57
+ """
58
+ content_type: Optional[str]
59
+ """The SHA256 value of the target file."""
60
+ sha256: Optional[str]
61
+ """The size of the target file in bytes."""
62
+ size: Optional[int]
63
+
64
+
65
+ class FileActivityUser(TypedDict):
66
+ """
67
+ A unique identifier for the user.
68
+
69
+ Defaults to the logged in Django User.username if not provided.
70
+ """
71
+
72
+ username: Optional[str]
73
+
74
+
75
+ def log_file_activity(
76
+ request: HttpRequest,
77
+ event: FileActivityEvent,
78
+ result: Result,
79
+ file: FileActivityFile,
80
+ user: Optional[FileActivityUser] = None,
81
+ server: Optional[Server] = None,
82
+ client: Optional[Client] = None,
83
+ severity: Optional[Severity] = None,
84
+ time_generated: Optional[datetime.datetime] = None,
85
+ result_details: Optional[str] = None,
86
+ message: Optional[str] = None,
87
+ ):
88
+ """
89
+ Log an ASIM File Event to standard output.
90
+
91
+ :param request: django.http.HttpRequest object which initiated this Authentication request
92
+ from which the following data will be logged if available
93
+ - Django Authentication systems current username
94
+ - Client IP address
95
+ - Server hostname
96
+ :param event: What File Event action was attempted, one of:
97
+ - FileAccessed
98
+ - FileCreated
99
+ - FileModified
100
+ - FileDeleted
101
+ - FileRenamed
102
+ - FileCopied
103
+ - FileMoved
104
+ - FolderCreated
105
+ - FolderDeleted
106
+ - FolderMoved
107
+ - FolderModified
108
+ :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
109
+ :param file: Dictionary containing information on the target of this File event see
110
+ FileActivityFile for more details.
111
+ :param user: Dictionary containing information on the logged in users username.
112
+ :param server: Dictionary containing information on the server servicing this File event
113
+ see Server class for more details.
114
+ :param client: Dictionary containing information on the client performing this File event
115
+ see Client class for more details.
116
+ :param severity: Optional severity of the event, defaults to "Informational", otherwise one of:
117
+ - "Informational"
118
+ - "Low"
119
+ - "Medium"
120
+ - "High"
121
+ :param time_generated: Optional datetime for when the event happened, otherwise datetime.now
122
+ :param result_details: Optional string describing any details associated with the events outcome.
123
+ This field is typically populated when the result is a failure.
124
+ :param message: Optional string describing the reason why the log was generated.
125
+
126
+ See also: https://learn.microsoft.com/en-us/azure/sentinel/normalization-schema-file-event
127
+ """
128
+ if user == None:
129
+ user = {}
130
+ if server == None:
131
+ server = {}
132
+ if client == None:
133
+ client = {}
134
+
135
+ event_created = time_generated or datetime.datetime.now(tz=datetime.timezone.utc)
136
+
137
+ log = {
138
+ "EventSchema": "FileEvent",
139
+ "EventSchemaVersion": "0.2.1",
140
+ "EventType": event,
141
+ "EventResult": result,
142
+ "EventCreated": event_created.isoformat(), # TODO: Should this really be EventCreated, or TimeGenerated
143
+ "EventSeverity": severity or _default_severity(result),
144
+ "TargetFilePath": file["path"],
145
+ }
146
+
147
+ if "name" in file:
148
+ log["TargetFileName"] = file["name"]
149
+ else:
150
+ log["TargetFileName"] = os.path.basename(file["path"])
151
+
152
+ if "extension" in file:
153
+ log["TargetFileExtension"] = file["extension"]
154
+ else:
155
+ file_name_parts = list(filter(None, log["TargetFileName"].split(".", 1)))
156
+ if len(file_name_parts) > 1:
157
+ log["TargetFileExtension"] = file_name_parts[1]
158
+
159
+ if "content_type" in file:
160
+ log["TargetFileMimeType"] = file["content_type"]
161
+
162
+ if "sha256" in file:
163
+ log["TargetFileSHA256"] = file["sha256"]
164
+
165
+ if "size" in file:
166
+ log["TargetFileSize"] = file["size"]
167
+
168
+ if "hostname" in server:
169
+ log["DvcHostname"] = server["hostname"]
170
+ elif hasattr(request, "environ") and "SERVER_NAME" in request.environ:
171
+ log["DvcHostname"] = request.environ["SERVER_NAME"]
172
+
173
+ if "ip_address" in client:
174
+ 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
+
178
+ if "username" in user:
179
+ log["ActorUsername"] = user["username"]
180
+ elif request.user.username:
181
+ log["ActorUsername"] = request.user.username
182
+
183
+ if result_details:
184
+ log["EventResultDetails"] = result_details
185
+
186
+ if message:
187
+ log["EventMessage"] = message
188
+
189
+ if "ip_address" in server:
190
+ log["DvcIpAddr"] = server["ip_address"]
191
+
192
+ print(json.dumps(log), flush=True)
193
+
194
+
195
+ log_file_activity.Event = FileActivityEvent
196
+ log_file_activity.Result = Result
197
+ log_file_activity.Severity = Severity
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-log-formatter-asim
3
- Version: 1.0.0
3
+ Version: 1.1.0a1
4
4
  Summary: Formats Django logs in ASIM format.
5
5
  License: MIT
6
6
  Author: Department for Business and Trade Platform Team
@@ -16,7 +16,6 @@ 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: pre-commit (>=3.5.0,<4.0.0)
20
19
  Description-Content-Type: text/markdown
21
20
 
22
21
  # Django ASIM log formatter
@@ -0,0 +1,9 @@
1
+ django_log_formatter_asim/__init__.py,sha256=i-7HqYE5s3hjmHLh4TTzLpEdgr5N22msFEQw16Pe_EI,7867
2
+ django_log_formatter_asim/events/__init__.py,sha256=th8AEFNM-J5lNlO-d8Lk465jXqplE3IoTwj4DlscwYo,92
3
+ django_log_formatter_asim/events/authentication.py,sha256=Nkgv-c_pNhwS8C1QYj0M3zH_cz2QJQqopzcMRhTv7Ls,6242
4
+ django_log_formatter_asim/events/common.py,sha256=4P3lb-rfxv_4Vf2DfW-Mi3Sq7rIWqF_tGK8mPPCUMco,1039
5
+ django_log_formatter_asim/events/file_activity.py,sha256=bKDpZcoBUePTeKCDPqLToGc8CA85_aXTw1c8KXoRLq0,6741
6
+ django_log_formatter_asim-1.1.0a1.dist-info/LICENSE,sha256=dP79lN73--7LMApnankTGLqDbImXg8iYFqWgnExGkGk,1090
7
+ django_log_formatter_asim-1.1.0a1.dist-info/METADATA,sha256=Fz6KC4Ai4jr3yq-Q1PDCtl0cp9mGlor0buzMCmh9J9s,5535
8
+ django_log_formatter_asim-1.1.0a1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
+ django_log_formatter_asim-1.1.0a1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- django_log_formatter_asim/__init__.py,sha256=i-7HqYE5s3hjmHLh4TTzLpEdgr5N22msFEQw16Pe_EI,7867
2
- django_log_formatter_asim-1.0.0.dist-info/LICENSE,sha256=dP79lN73--7LMApnankTGLqDbImXg8iYFqWgnExGkGk,1090
3
- django_log_formatter_asim-1.0.0.dist-info/METADATA,sha256=Gmn92QLejMsh63aivEaeqLfOKfnGl-9Hw5MPO-YC5KQ,5576
4
- django_log_formatter_asim-1.0.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
5
- django_log_formatter_asim-1.0.0.dist-info/RECORD,,