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.
- django_log_formatter_asim/events/__init__.py +7 -2
- django_log_formatter_asim/events/account_management.py +133 -0
- django_log_formatter_asim/events/authentication.py +133 -141
- django_log_formatter_asim/events/common.py +80 -12
- django_log_formatter_asim/events/file_activity.py +159 -160
- django_log_formatter_asim-1.2.0.dist-info/METADATA +399 -0
- django_log_formatter_asim-1.2.0.dist-info/RECORD +11 -0
- {django_log_formatter_asim-1.1.0a4.dist-info → django_log_formatter_asim-1.2.0.dist-info}/WHEEL +1 -1
- django_log_formatter_asim-1.1.0a4.dist-info/METADATA +0 -176
- django_log_formatter_asim-1.1.0a4.dist-info/RECORD +0 -10
- {django_log_formatter_asim-1.1.0a4.dist-info → django_log_formatter_asim-1.2.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -7,14 +7,12 @@ from typing import TypedDict
|
|
|
7
7
|
|
|
8
8
|
from django.http import HttpRequest
|
|
9
9
|
|
|
10
|
-
from
|
|
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
|
|
35
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
|
66
|
+
"""The SHA256 value of the file."""
|
|
63
67
|
sha256: Optional[str]
|
|
64
|
-
"""The size of the
|
|
68
|
+
"""The size of the file in bytes."""
|
|
65
69
|
size: Optional[int]
|
|
66
70
|
|
|
67
71
|
|
|
68
|
-
class
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
log
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
log
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
log
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|