django-log-formatter-asim 1.1.0a4__py3-none-any.whl → 1.2.0__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.
@@ -7,14 +7,12 @@ from typing import TypedDict
7
7
 
8
8
  from django.http import HttpRequest
9
9
 
10
- from django_log_formatter_asim.ecs import _get_container_id
11
-
10
+ from .common import Activity
12
11
  from .common import Client
12
+ from .common import LoggedInUser
13
13
  from .common import Result
14
14
  from .common import Server
15
15
  from .common import Severity
16
- from .common import _default_severity
17
- from .common import _get_client_ip_address
18
16
 
19
17
 
20
18
  class FileActivityEvent(str, Enum):
@@ -31,24 +29,30 @@ class FileActivityEvent(str, Enum):
31
29
  FolderModified = "FolderModified"
32
30
 
33
31
 
34
- class FileActivityFile(TypedDict):
35
- """Dictionary to represent properties of the target file."""
32
+ class FileActivityFileBase(TypedDict):
33
+ """Mandatory field definitions of FileActivityFile."""
36
34
 
37
35
  """
38
- The full, normalized path of the target file, including the folder or location,
39
- the file name, and the extension.
36
+ The full, normalized path of the target file, including the folder or
37
+ location, the file name, and the extension.
40
38
  """
41
39
  path: str
40
+
41
+
42
+ class FileActivityFile(FileActivityFileBase, total=False):
43
+ """Dictionary to represent properties of either the target or source
44
+ file."""
45
+
42
46
  """
43
47
  The name of the target file, without a path or a location, but with an
44
48
  extension if available. This field should be similar to the final element in
45
- the TargetFilePath field.
49
+ the *FilePath field.
46
50
 
47
51
  Defaults to extracting the name based off the path if not provided.
48
52
  """
49
53
  name: Optional[str]
50
54
  """
51
- The target file extension.
55
+ The file extension.
52
56
 
53
57
  Defaults to extracting the extension based off the path if not provided.
54
58
  """
@@ -59,157 +63,152 @@ class FileActivityFile(TypedDict):
59
63
  Allowed values are listed in the IANA Media Types repository.
60
64
  """
61
65
  content_type: Optional[str]
62
- """The SHA256 value of the target file."""
66
+ """The SHA256 value of the file."""
63
67
  sha256: Optional[str]
64
- """The size of the target file in bytes."""
68
+ """The size of the file in bytes."""
65
69
  size: Optional[int]
66
70
 
67
71
 
68
- class FileActivityUser(TypedDict):
69
- """
70
- A unique identifier for the user.
71
-
72
- Defaults to the logged in Django User.username if not provided.
73
- """
74
-
75
- username: Optional[str]
76
-
77
-
78
- def log_file_activity(
79
- request: HttpRequest,
80
- event: FileActivityEvent,
81
- result: Result,
82
- file: FileActivityFile,
83
- user: Optional[FileActivityUser] = None,
84
- server: Optional[Server] = None,
85
- client: Optional[Client] = None,
86
- severity: Optional[Severity] = None,
87
- time_generated: Optional[datetime.datetime] = None,
88
- result_details: Optional[str] = None,
89
- message: Optional[str] = None,
90
- ):
91
- """
92
- Log an ASIM File Event to standard output.
93
-
94
- :param request: django.http.HttpRequest object which initiated this Authentication request
95
- from which the following data will be logged if available
96
- - Django Authentication systems current username
97
- - Client IP address
98
- - URL requested by the client
99
- - Server domain name
100
- :param event: What File Event action was attempted, one of:
101
- - FileAccessed
102
- - FileCreated
103
- - FileModified
104
- - FileDeleted
105
- - FileRenamed
106
- - FileCopied
107
- - FileMoved
108
- - FolderCreated
109
- - FolderDeleted
110
- - FolderMoved
111
- - FolderModified
112
- :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
113
- :param file: Dictionary containing information on the target of this File event see
114
- FileActivityFile for more details.
115
- :param user: Dictionary containing information on the logged in users username.
116
- :param server: Dictionary containing information on the server servicing this File event
117
- see Server class for more details.
118
- :param client: Dictionary containing information on the client performing this File event
119
- see Client class for more details.
120
- :param severity: Optional severity of the event, defaults to "Informational", otherwise one of:
121
- - "Informational"
122
- - "Low"
123
- - "Medium"
124
- - "High"
125
- :param time_generated: Optional datetime for when the event happened, otherwise datetime.now
126
- :param result_details: Optional string describing any details associated with the events outcome.
127
- This field is typically populated when the result is a failure.
128
- :param message: Optional string describing the reason why the log was generated.
129
-
130
- See also: https://learn.microsoft.com/en-us/azure/sentinel/normalization-schema-file-event
131
- """
132
- if user == None:
133
- user = {}
134
- if server == None:
135
- server = {}
136
- if client == None:
137
- client = {}
138
-
139
- event_created = time_generated or datetime.datetime.now(tz=datetime.timezone.utc)
140
-
141
- log = {
142
- "EventSchema": "FileEvent",
143
- "EventSchemaVersion": "0.2.1",
144
- "EventType": event,
145
- "EventResult": result,
146
- "EventCreated": event_created.isoformat(), # TODO: Should this really be EventCreated, or TimeGenerated
147
- "EventSeverity": severity or _default_severity(result),
148
- "TargetFilePath": file["path"],
149
- }
150
-
151
- if "name" in file:
152
- log["TargetFileName"] = file["name"]
153
- else:
154
- log["TargetFileName"] = os.path.basename(file["path"])
155
-
156
- if "extension" in file:
157
- log["TargetFileExtension"] = file["extension"]
158
- else:
159
- file_name_parts = list(filter(None, log["TargetFileName"].split(".", 1)))
160
- if len(file_name_parts) > 1:
161
- log["TargetFileExtension"] = file_name_parts[1]
162
-
163
- if "content_type" in file:
164
- log["TargetFileMimeType"] = file["content_type"]
165
-
166
- if "sha256" in file:
167
- log["TargetFileSHA256"] = file["sha256"]
168
-
169
- if "size" in file:
170
- log["TargetFileSize"] = file["size"]
171
-
172
- if "domain_name" in server:
173
- log["HttpHost"] = server["domain_name"]
174
- elif "HTTP_HOST" in request.META:
175
- log["HttpHost"] = request.get_host()
176
-
177
- if "service_name" in server:
178
- log["TargetAppName"] = server["service_name"]
179
- elif os.environ.get("COPILOT_APPLICATION_NAME") and os.environ.get("COPILOT_SERVICE_NAME"):
180
- app_name = f"{os.environ['COPILOT_APPLICATION_NAME']}-{os.environ['COPILOT_SERVICE_NAME']}"
181
- log["TargetAppName"] = app_name
182
-
183
- if container_id := _get_container_id():
184
- log["ContainerId"] = container_id
185
-
186
- if "ip_address" in client:
187
- log["SrcIpAddr"] = client["ip_address"]
188
- elif client_ip := _get_client_ip_address(request):
189
- log["SrcIpAddr"] = client_ip
190
-
191
- if "requested_url" in client:
192
- log["TargetUrl"] = client["requested_url"]
193
- elif "HTTP_HOST" in request.META:
194
- log["TargetUrl"] = request.scheme + "://" + request.get_host() + request.get_full_path()
195
-
196
- if "username" in user:
197
- log["TargetUsername"] = user["username"]
198
- elif hasattr(request, "user") and request.user.username:
199
- log["TargetUsername"] = request.user.username
200
-
201
- if result_details:
202
- log["EventResultDetails"] = result_details
203
-
204
- if message:
205
- log["EventMessage"] = message
206
-
207
- if "ip_address" in server:
208
- log["DvcIpAddr"] = server["ip_address"]
209
-
210
- print(json.dumps(log), flush=True)
211
-
212
-
213
- log_file_activity.Event = FileActivityEvent
214
- log_file_activity.Result = Result
215
- log_file_activity.Severity = Severity
72
+ class LogFileActivity(Activity):
73
+ Event = FileActivityEvent
74
+ Result = Result
75
+ Severity = Severity
76
+
77
+ def __call__(
78
+ self,
79
+ request: HttpRequest,
80
+ event: FileActivityEvent,
81
+ result: Result,
82
+ file: FileActivityFile,
83
+ source_file: Optional[FileActivityFile] = None,
84
+ user: Optional[LoggedInUser] = None,
85
+ server: Optional[Server] = None,
86
+ client: Optional[Client] = None,
87
+ severity: Optional[Severity] = None,
88
+ time_generated: Optional[datetime.datetime] = None,
89
+ result_details: Optional[str] = None,
90
+ message: Optional[str] = None,
91
+ ):
92
+ """
93
+ Log an ASIM File Event to standard output.
94
+
95
+ :param request: django.http.HttpRequest object which initiated this Authentication request
96
+ from which the following data will be logged if available
97
+ - Django Authentication systems current username
98
+ - Client IP address
99
+ - URL requested by the client
100
+ - Server domain name
101
+ :param event: What File Event action was attempted, one of:
102
+ - FileAccessed
103
+ - FileCreated
104
+ - FileModified
105
+ - FileDeleted
106
+ - FileRenamed
107
+ - FileCopied
108
+ - FileMoved
109
+ - FolderCreated
110
+ - FolderDeleted
111
+ - FolderMoved
112
+ - FolderModified
113
+ :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
114
+ :param file: Dictionary containing information on the target of this File event see
115
+ FileActivityFile for more details.
116
+ :param source_file: Dictionary containing information on the source of this File event,
117
+ this MUST be used for a FileRenamed, FileMoved, FileCopied, FolderMoved
118
+ operation. See FileActivityFile for more details.
119
+ :param user: Dictionary containing information on the logged in users username.
120
+ :param server: Dictionary containing information on the server servicing this File event
121
+ see Server class for more details.
122
+ :param client: Dictionary containing information on the client performing this File event
123
+ see Client class for more details.
124
+ :param severity: Optional severity of the event, defaults to "Informational", otherwise one of:
125
+ - "Informational"
126
+ - "Low"
127
+ - "Medium"
128
+ - "High"
129
+ :param time_generated: Optional datetime for when the event happened, otherwise datetime.now
130
+ :param result_details: Optional string describing any details associated with the events outcome.
131
+ This field is typically populated when the result is a failure.
132
+ :param message: Optional string describing the reason why the log was generated.
133
+
134
+ See also: https://learn.microsoft.com/en-us/azure/sentinel/normalization-schema-file-event
135
+ """
136
+
137
+ self._log_file_activity(
138
+ request,
139
+ event,
140
+ result,
141
+ file,
142
+ source_file,
143
+ user={} if user == None else user,
144
+ server={} if server == None else server,
145
+ client={} if client == None else client,
146
+ event_created=time_generated or datetime.datetime.now(tz=datetime.timezone.utc),
147
+ severity=severity,
148
+ result_details=result_details,
149
+ message=message,
150
+ )
151
+
152
+ def _log_file_activity(
153
+ self,
154
+ request: HttpRequest,
155
+ event: FileActivityEvent,
156
+ result: Result,
157
+ file: FileActivityFile,
158
+ source_file: Optional[FileActivityFile],
159
+ user: LoggedInUser,
160
+ server: Server,
161
+ client: Client,
162
+ event_created: datetime.datetime,
163
+ severity: Optional[Severity] = None,
164
+ result_details: Optional[str] = None,
165
+ message: Optional[str] = None,
166
+ ):
167
+ log = {
168
+ "EventSchema": "FileEvent",
169
+ "EventSchemaVersion": "0.2.1",
170
+ "EventType": event,
171
+ }
172
+
173
+ log.update(
174
+ self._activity_fields(
175
+ request, event_created, result, server, client, severity, result_details, message
176
+ )
177
+ )
178
+
179
+ log.update(self._generate_file_attributes(file, "Target"))
180
+ if source_file:
181
+ log.update(self._generate_file_attributes(source_file, "Src"))
182
+
183
+ if "username" in user:
184
+ log["TargetUsername"] = user["username"]
185
+ elif hasattr(request, "user") and request.user.username:
186
+ log["TargetUsername"] = request.user.username
187
+
188
+ print(json.dumps(log), flush=True)
189
+
190
+ def _generate_file_attributes(self, file: FileActivityFile, prefix: str) -> dict:
191
+ log = {prefix + "FilePath": file["path"]}
192
+
193
+ if "name" in file:
194
+ log[prefix + "FileName"] = file["name"]
195
+ else:
196
+ log[prefix + "FileName"] = os.path.basename(file["path"])
197
+
198
+ if "extension" in file:
199
+ log[prefix + "FileExtension"] = file["extension"]
200
+ else:
201
+ file_name_parts = list(filter(None, log[prefix + "FileName"].split(".", 1)))
202
+ if len(file_name_parts) > 1:
203
+ log[prefix + "FileExtension"] = file_name_parts[1]
204
+
205
+ if "content_type" in file:
206
+ log[prefix + "FileMimeType"] = file["content_type"]
207
+
208
+ if "sha256" in file:
209
+ log[prefix + "FileSHA256"] = file["sha256"]
210
+
211
+ if "size" in file:
212
+ log[prefix + "FileSize"] = file["size"]
213
+
214
+ return log