mmisp-lib 0.3.0__tar.gz → 0.4.0__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.
Files changed (127) hide show
  1. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/PKG-INFO +2 -2
  2. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/pyproject.toml +3 -2
  3. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/attributes.py +12 -11
  4. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/events.py +7 -5
  5. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/organisations.py +1 -1
  6. mmisp_lib-0.4.0/src/mmisp/db/additional_properties.py +10 -0
  7. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/all_models.py +2 -0
  8. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/mixins.py +2 -1
  9. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/attribute.py +81 -3
  10. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/event.py +35 -0
  11. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/galaxy.py +7 -0
  12. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/galaxy_cluster.py +32 -0
  13. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/object.py +14 -0
  14. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/organisation.py +28 -0
  15. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/tag.py +4 -0
  16. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/user.py +4 -0
  17. mmisp_lib-0.4.0/src/mmisp/lib/attribute_search_filter.py +60 -0
  18. mmisp_lib-0.4.0/src/mmisp/tests/compatibility_helpers.py +56 -0
  19. mmisp_lib-0.4.0/src/mmisp/tests/fixtures.py +787 -0
  20. mmisp_lib-0.4.0/src/mmisp/tests/generators/feed_generator.py +82 -0
  21. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/attribute_generator.py +5 -0
  22. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/auth_key_generator.py +18 -0
  23. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/event_generator.py +16 -0
  24. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/galaxy_generator.py +11 -0
  25. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/identity_provider_generator.py +16 -0
  26. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/noticelist_generator.py +26 -0
  27. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/organisation_generator.py +20 -0
  28. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/role_generator.py +106 -0
  29. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/server_generator.py +27 -0
  30. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/sharing_group_generator.py +16 -0
  31. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/tag_generator.py +15 -0
  32. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/taxonomy_generator.py +40 -0
  33. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/user_generator.py +27 -0
  34. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/user_setting_generator.py +20 -0
  35. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/warninglist_generator.py +31 -0
  36. mmisp_lib-0.4.0/src/mmisp/tests/generators/model_generators/workflow_generator.py +414 -0
  37. mmisp_lib-0.4.0/src/mmisp/tests/generators/object_generator.py +180 -0
  38. mmisp_lib-0.4.0/src/mmisp/tests/generators/sighting_generator.py +47 -0
  39. mmisp_lib-0.4.0/src/mmisp/tests/generators/tag_generator.py +103 -0
  40. mmisp_lib-0.4.0/src/mmisp/tests/generators/taxonomies_generator.py +61 -0
  41. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/execution.py +5 -3
  42. mmisp_lib-0.4.0/src/mmisp/workflows/py.typed +0 -0
  43. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp_lib.egg-info/PKG-INFO +2 -2
  44. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp_lib.egg-info/SOURCES.txt +26 -0
  45. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp_lib.egg-info/requires.txt +1 -1
  46. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/tests/test_commandline_tool.py +1 -1
  47. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/LICENSE +0 -0
  48. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/README.md +0 -0
  49. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/setup.cfg +0 -0
  50. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/__init__.py +0 -0
  51. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/auth_keys.py +0 -0
  52. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/authentication.py +0 -0
  53. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/common.py +0 -0
  54. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/feeds.py +0 -0
  55. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/galaxies.py +0 -0
  56. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/galaxy_clusters.py +0 -0
  57. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/jobs.py +0 -0
  58. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/logs.py +0 -0
  59. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/noticelists.py +0 -0
  60. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/objects.py +0 -0
  61. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/py.typed +0 -0
  62. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/responses/__init__.py +0 -0
  63. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/responses/check_graph_response.py +0 -0
  64. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/responses/standard_status_response.py +0 -0
  65. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/roles.py +0 -0
  66. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/servers.py +0 -0
  67. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/sharing_groups.py +0 -0
  68. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/sightings.py +0 -0
  69. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/statistics.py +0 -0
  70. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/tags.py +0 -0
  71. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/taxonomies.py +0 -0
  72. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/user_settings.py +0 -0
  73. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/users.py +0 -0
  74. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/warninglists.py +0 -0
  75. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/api_schemas/workflows.py +0 -0
  76. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/__init__.py +0 -0
  77. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/main.py +0 -0
  78. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/organisation.py +0 -0
  79. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/py.typed +0 -0
  80. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/setup.py +0 -0
  81. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/commandline_tool/user.py +0 -0
  82. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/__init__.py +0 -0
  83. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/config.py +0 -0
  84. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/database.py +0 -0
  85. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/__init__.py +0 -0
  86. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/admin_setting.py +0 -0
  87. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/auth_key.py +0 -0
  88. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/feed.py +0 -0
  89. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/identity_provider.py +0 -0
  90. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/log.py +0 -0
  91. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/noticelist.py +0 -0
  92. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/role.py +0 -0
  93. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/server.py +0 -0
  94. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/sharing_group.py +0 -0
  95. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/sighting.py +0 -0
  96. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/taxonomy.py +0 -0
  97. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/user_setting.py +0 -0
  98. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/warninglist.py +0 -0
  99. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/workflow.py +0 -0
  100. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/models/workflow_blueprint.py +0 -0
  101. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/mypy.py +0 -0
  102. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/print_changes.py +0 -0
  103. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/db/py.typed +0 -0
  104. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/__init__.py +0 -0
  105. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/actions.py +0 -0
  106. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/attributes.py +0 -0
  107. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/logging.py +0 -0
  108. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/permissions.py +0 -0
  109. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/py.typed +0 -0
  110. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/lib/uuid.py +0 -0
  111. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/plugins/py.typed +0 -0
  112. {mmisp_lib-0.3.0/src/mmisp/util → mmisp_lib-0.4.0/src/mmisp/tests}/__init__.py +0 -0
  113. {mmisp_lib-0.3.0/src/mmisp/workflows → mmisp_lib-0.4.0/src/mmisp/util}/__init__.py +0 -0
  114. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/util/crypto.py +0 -0
  115. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/util/models.py +0 -0
  116. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/util/partial.py +0 -0
  117. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/util/py.typed +0 -0
  118. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/util/uuid.py +0 -0
  119. /mmisp_lib-0.3.0/src/mmisp/workflows/py.typed → /mmisp_lib-0.4.0/src/mmisp/workflows/__init__.py +0 -0
  120. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/fastapi.py +0 -0
  121. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/graph.py +0 -0
  122. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/input.py +0 -0
  123. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/legacy.py +0 -0
  124. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/misp_core_format.py +0 -0
  125. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp/workflows/modules.py +0 -0
  126. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp_lib.egg-info/dependency_links.txt +0 -0
  127. {mmisp_lib-0.3.0 → mmisp_lib-0.4.0}/src/mmisp_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmisp-lib
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Requires-Python: >=3.11.0
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -27,7 +27,7 @@ Requires-Dist: mypy==1.8.0; extra == "dev"
27
27
  Requires-Dist: pre-commit==3.6.0; extra == "dev"
28
28
  Requires-Dist: pytest==8.0.0; extra == "dev"
29
29
  Requires-Dist: pytest-test-groups; extra == "dev"
30
- Requires-Dist: pytest-asyncio==0.23.5.post1; extra == "dev"
30
+ Requires-Dist: pytest-asyncio==0.21.2; extra == "dev"
31
31
  Requires-Dist: pytest-cov==4.1.0; extra == "dev"
32
32
  Requires-Dist: respx==0.20.2; extra == "dev"
33
33
  Requires-Dist: mysql-connector-python==8.3.0; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mmisp-lib"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = ""
5
5
  authors = []
6
6
  readme = "README.md"
@@ -35,7 +35,7 @@ dev = [
35
35
  "pre-commit==3.6.0",
36
36
  "pytest==8.0.0",
37
37
  "pytest-test-groups",
38
- "pytest-asyncio==0.23.5.post1",
38
+ "pytest-asyncio==0.21.2",
39
39
  "pytest-cov==4.1.0",
40
40
  "respx==0.20.2",
41
41
  "mysql-connector-python==8.3.0",
@@ -63,6 +63,7 @@ ignore = ["ANN002", "ANN003", "ANN401"]
63
63
 
64
64
  [tool.ruff.lint.per-file-ignores]
65
65
  "tests/**" = ["ANN001", "ANN201", "ANN202", "S101"]
66
+ "src/mmisp/tests/**" = ["ANN001", "ANN201", "ANN202", "S101"]
66
67
 
67
68
  [tool.coverage.run]
68
69
  concurrency = ["greenlet", "thread"]
@@ -34,6 +34,7 @@ class SearchAttributesEvent(BaseModel):
34
34
  info: str
35
35
  orgc_id: str
36
36
  uuid: str
37
+ publish_timestamp: int
37
38
 
38
39
 
39
40
  class SearchAttributesAttributesDetails(BaseModel):
@@ -56,7 +57,7 @@ class SearchAttributesAttributesDetails(BaseModel):
56
57
  last_seen: str | None = None
57
58
  Event: SearchAttributesEvent | None = None
58
59
  Object: SearchAttributesObject | None = None
59
- Tag: list[GetAttributeTag] = []
60
+ Tag: list[GetAttributeTag] | None = None
60
61
 
61
62
 
62
63
  class SearchAttributesAttributes(BaseModel):
@@ -86,10 +87,7 @@ class SearchAttributesModelOverrides(BaseModel):
86
87
  base_score_config: SearchAttributesModelOverridesBaseScoreConfig
87
88
 
88
89
 
89
- class SearchAttributesBody(BaseModel):
90
- returnFormat: str = "json"
91
- page: int | None = None
92
- limit: int | None = None
90
+ class RestSearchFilter(BaseModel):
93
91
  value: str | None = None
94
92
  value1: str | None = None
95
93
  value2: str | None = None
@@ -101,15 +99,21 @@ class SearchAttributesBody(BaseModel):
101
99
  to: str | None = None
102
100
  last: int | None = None
103
101
  eventid: str | None = None
102
+ published: bool | None = None
103
+ to_ids: bool | None = None
104
+ deleted: bool | None = None
105
+
106
+
107
+ class SearchAttributesBody(RestSearchFilter):
108
+ returnFormat: str = "json"
109
+ page: int | None = None
110
+ limit: int | None = None
104
111
  with_attachments: Annotated[bool | None, Field(alias="withAttachments")] = None
105
112
  uuid: str | None = None
106
113
  publish_timestamp: str | None = None
107
- published: bool | None = None
108
114
  timestamp: str | None = None
109
115
  attribute_timestamp: str | None = None
110
116
  enforce_warninglist: Annotated[bool | None, Field(alias="enforceWarninglist")]
111
- to_ids: bool | None = None
112
- deleted: bool | None = None
113
117
  event_timestamp: str | None = None
114
118
  threat_level_id: str | None = None
115
119
  eventinfo: str | None = None
@@ -134,9 +138,6 @@ class SearchAttributesBody(BaseModel):
134
138
  include_full_model: Annotated[bool | None, Field(alias="includeFullModel")] = None
135
139
  exclude_decayed: Annotated[bool | None, Field(alias="excludeDecayed")] = None
136
140
 
137
- class Config:
138
- orm_mode = True
139
-
140
141
 
141
142
  class RestoreAttributeResponse(BaseModel):
142
143
  id: str
@@ -1,3 +1,5 @@
1
+ from datetime import datetime
2
+
1
3
  from pydantic import BaseModel, PositiveInt, conint
2
4
 
3
5
  from mmisp.api_schemas.organisations import Organisation
@@ -89,7 +91,7 @@ class GetAllEventsGalaxyCluster(BaseModel):
89
91
  meta: AddEditGetEventGalaxyClusterMeta | None = None
90
92
  tag_id: str
91
93
  local: bool | None = None
92
- relationship_type: str | None = None
94
+ relationship_type: bool | str | None = None
93
95
 
94
96
 
95
97
  class AddEditGetEventGalaxyClusterRelationTag(BaseModel):
@@ -149,7 +151,7 @@ class AddEditGetEventGalaxyCluster(BaseModel):
149
151
  attribute_tag_id: str | None = None
150
152
  event_tag_id: str | None = None
151
153
  local: bool | None = None
152
- relationship_type: str = ""
154
+ relationship_type: bool | str = ""
153
155
 
154
156
 
155
157
  class AddEditGetEventGalaxy(BaseModel):
@@ -186,7 +188,7 @@ class AddEditGetEventTag(BaseModel):
186
188
  is_custom_galaxy: bool
187
189
  local_only: bool
188
190
  local: bool
189
- relationship_type: str | None = None
191
+ relationship_type: bool | str | None = None
190
192
 
191
193
 
192
194
  class AddEditGetEventAttribute(BaseModel):
@@ -288,7 +290,7 @@ class AddEditGetEventResponse(BaseModel):
288
290
  Event: AddEditGetEventDetails
289
291
 
290
292
  class Config:
291
- orm_mode = True
293
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
292
294
 
293
295
 
294
296
  class GetAllEventsOrg(BaseModel):
@@ -456,7 +458,7 @@ class GetAllEventsEventTag(BaseModel):
456
458
  event_id: str
457
459
  tag_id: str
458
460
  local: bool
459
- relationship_type: str | None = None
461
+ relationship_type: bool | str | None = None
460
462
  Tag: GetAllEventsEventTagTag | None = None
461
463
 
462
464
 
@@ -21,7 +21,7 @@ class Organisation(BaseModel):
21
21
  landingpage: str | None = None
22
22
 
23
23
  class Config:
24
- orm_mode = True
24
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
25
25
 
26
26
 
27
27
  class GetOrganisationResponse(BaseModel):
@@ -0,0 +1,10 @@
1
+ from sqlalchemy import func, select
2
+ from sqlalchemy.orm import column_property
3
+
4
+ from mmisp.db.models.organisation import Organisation
5
+ from mmisp.db.models.user import User
6
+
7
+ Organisation.user_count = column_property(
8
+ select(func.count(User.id)).where(User.org_id == Organisation.id).correlate_except(User).scalar_subquery(),
9
+ deferred=False,
10
+ )
@@ -13,3 +13,5 @@ model_module_names = map(".".join, zip(itertools.repeat(model_pkg), all_models))
13
13
 
14
14
  for m in model_module_names:
15
15
  importlib.import_module(m)
16
+
17
+ importlib.import_module("mmisp.db.additional_properties")
@@ -6,9 +6,10 @@ from sqlalchemy.ext.hybrid import hybrid_property
6
6
 
7
7
  class DictMixin:
8
8
  def asdict(self: Self) -> dict:
9
+ unloaded = inspect(self).unloaded
9
10
  d = {}
10
11
  for key in self.__mapper__.c.keys(): # type:ignore[attr-defined]
11
- if not key.startswith("_"):
12
+ if not key.startswith("_") and key not in unloaded:
12
13
  d[key] = getattr(self, key)
13
14
 
14
15
  for key, prop in inspect(self.__class__).all_orm_descriptors.items(): # type:ignore[union-attr]
@@ -1,7 +1,9 @@
1
+ import typing
1
2
  from typing import Self, Type
2
3
 
3
- from sqlalchemy import BigInteger, Boolean, ForeignKey, Integer, String, Text
4
- from sqlalchemy.ext.hybrid import hybrid_property
4
+ from sqlalchemy import BigInteger, Boolean, ForeignKey, Integer, String, Text, or_
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+ from sqlalchemy.ext.hybrid import Comparator, hybrid_property
5
7
  from sqlalchemy.orm import relationship
6
8
  from sqlalchemy.orm.decl_api import DeclarativeMeta
7
9
 
@@ -14,6 +16,20 @@ from ..database import Base
14
16
  from .event import Event
15
17
  from .tag import Tag
16
18
 
19
+ if typing.TYPE_CHECKING:
20
+ from sqlalchemy import ColumnExpressionArgument
21
+ else:
22
+ ColumnExpressionArgument = typing.Any
23
+
24
+
25
+ class AttributeComparator(Comparator):
26
+ def __init__(self: Self, cls: typing.Any) -> None:
27
+ self.cls = cls
28
+
29
+ def __eq__(self: Self, other: typing.Any) -> ColumnExpressionArgument:
30
+ # Overriding equality to check if the value matches either value1 or value1 + "|" + value2
31
+ return or_(self.cls.value1 == other, self.cls.value1 + "|" + self.cls.value2 == other)
32
+
17
33
 
18
34
  class Attribute(Base, DictMixin):
19
35
  __tablename__ = "attributes"
@@ -40,11 +56,57 @@ class Attribute(Base, DictMixin):
40
56
  last_seen: Mapped[int] = mapped_column(BigInteger, index=True)
41
57
 
42
58
  event = relationship("Event", back_populates="attributes", lazy="joined") # type:ignore[var-annotated]
59
+ mispobject = relationship(
60
+ "Object",
61
+ primaryjoin="Attribute.object_id == Object.id",
62
+ back_populates="attributes",
63
+ lazy="joined",
64
+ foreign_keys="Attribute.object_id",
65
+ ) # type:ignore[var-annotated]
66
+ tags = relationship("Tag", secondary="attribute_tags", lazy="raise_on_sql", viewonly=True)
67
+ attributetags = relationship(
68
+ "AttributeTag",
69
+ primaryjoin="Attribute.id == AttributeTag.attribute_id",
70
+ back_populates="attribute",
71
+ lazy="raise_on_sql",
72
+ viewonly=True,
73
+ )
74
+ attributetags_galaxy = relationship(
75
+ "AttributeTag",
76
+ primaryjoin="and_(Attribute.id == AttributeTag.attribute_id, Tag.is_galaxy)",
77
+ secondary="join(AttributeTag, Tag, AttributeTag.tag_id == Tag.id)",
78
+ secondaryjoin="AttributeTag.tag_id == Tag.id",
79
+ lazy="raise_on_sql",
80
+ viewonly=True,
81
+ )
82
+
83
+ galaxy_tags = relationship(
84
+ "Tag",
85
+ secondary="attribute_tags",
86
+ secondaryjoin="and_(AttributeTag.tag_id == Tag.id, Tag.is_galaxy)",
87
+ lazy="raise_on_sql",
88
+ overlaps="tags, events",
89
+ viewonly=True,
90
+ )
91
+ local_tags = relationship(
92
+ "Tag",
93
+ secondary="attribute_tags",
94
+ secondaryjoin="and_(AttributeTag.tag_id == Tag.id, AttributeTag.local)",
95
+ lazy="raise_on_sql",
96
+ viewonly=True,
97
+ )
98
+ nonlocal_tags = relationship(
99
+ "Tag",
100
+ secondary="attribute_tags",
101
+ secondaryjoin="and_(AttributeTag.tag_id == Tag.id, not_(AttributeTag.local))",
102
+ lazy="raise_on_sql",
103
+ viewonly=True,
104
+ )
43
105
 
44
106
  __mapper_args__ = {"polymorphic_on": "type"}
45
107
 
46
108
  def __init__(self: Self, *arg, **kwargs) -> None:
47
- if kwargs["value1"] is None:
109
+ if "value" in kwargs:
48
110
  split_val = kwargs["value"].split("|", 1)
49
111
  kwargs["value1"] = split_val[0]
50
112
  if len(split_val) == 2:
@@ -52,6 +114,14 @@ class Attribute(Base, DictMixin):
52
114
 
53
115
  super().__init__(*arg, **kwargs)
54
116
 
117
+ async def add_tag(self: Self, db: AsyncSession, tag: "Tag", local: bool = False) -> "AttributeTag":
118
+ if tag.local_only:
119
+ local = True
120
+ attribute_tag = AttributeTag(attribute=self, tag=tag, event_id=self.event_id, local=local)
121
+ db.add(attribute_tag)
122
+ await db.commit()
123
+ return attribute_tag
124
+
55
125
  @property
56
126
  def event_uuid(self: "Attribute") -> str:
57
127
  return self.event.uuid
@@ -69,6 +139,10 @@ class Attribute(Base, DictMixin):
69
139
  if len(split) == 2:
70
140
  self.value2 = split[1]
71
141
 
142
+ @value.comparator
143
+ def value(cls: Self) -> AttributeComparator:
144
+ return AttributeComparator(cls)
145
+
72
146
 
73
147
  class AttributeTag(Base):
74
148
  __tablename__ = "attribute_tags"
@@ -80,6 +154,10 @@ class AttributeTag(Base):
80
154
  event_id: Mapped[int] = mapped_column(Integer, ForeignKey(Event.id, ondelete="CASCADE"), nullable=False, index=True)
81
155
  tag_id: Mapped[int] = mapped_column(Integer, ForeignKey(Tag.id, ondelete="CASCADE"), nullable=False, index=True)
82
156
  local: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
157
+ relationship_type: Mapped[str] = mapped_column(String(191), nullable=True)
158
+
159
+ attribute = relationship("Attribute", back_populates="attributetags", lazy="raise_on_sql")
160
+ tag = relationship("Tag", back_populates="attributetags", lazy="raise_on_sql")
83
161
 
84
162
 
85
163
  class AttributeMeta(DeclarativeMeta):
@@ -38,6 +38,37 @@ class Event(Base):
38
38
  protected: Mapped[bool] = mapped_column(Boolean)
39
39
 
40
40
  attributes = relationship("Attribute", back_populates="event") # type:ignore[assignment,var-annotated]
41
+ mispobjects = relationship("Object", back_populates="event") # type:ignore[assignment,var-annotated]
42
+ org = relationship(
43
+ "Organisation", primaryjoin="Event.org_id == Organisation.id", back_populates="events", lazy="raise_on_sql"
44
+ ) # type:ignore[assignment,var-annotated]
45
+ orgc = relationship(
46
+ "Organisation",
47
+ primaryjoin="Event.orgc_id == Organisation.id",
48
+ back_populates="events_created",
49
+ lazy="raise_on_sql",
50
+ ) # type:ignore[assignment,var-annotated]
51
+ creator = relationship("User", primaryjoin="Event.user_id == User.id", lazy="selectin")
52
+ tags = relationship("Tag", secondary="event_tags", lazy="raise_on_sql", viewonly=True)
53
+ eventtags = relationship(
54
+ "EventTag", primaryjoin="Event.id == EventTag.event_id", lazy="raise_on_sql", viewonly=True
55
+ )
56
+ eventtags_galaxy = relationship(
57
+ "EventTag",
58
+ primaryjoin="and_(Event.id == EventTag.event_id, Tag.is_galaxy)",
59
+ secondary="join(EventTag, Tag, EventTag.tag_id == Tag.id)",
60
+ secondaryjoin="EventTag.tag_id == Tag.id",
61
+ lazy="raise_on_sql",
62
+ viewonly=True,
63
+ )
64
+ galaxy_tags = relationship(
65
+ "Tag",
66
+ secondary="event_tags",
67
+ secondaryjoin="and_(EventTag.tag_id == Tag.id, Tag.is_galaxy)",
68
+ lazy="raise_on_sql",
69
+ overlaps="tags, events",
70
+ viewonly=True,
71
+ )
41
72
 
42
73
 
43
74
  class EventReport(Base):
@@ -61,3 +92,7 @@ class EventTag(Base):
61
92
  event_id: Mapped[int] = mapped_column(Integer, ForeignKey(Event.id, ondelete="CASCADE"), nullable=False, index=True)
62
93
  tag_id: Mapped[int] = mapped_column(Integer, ForeignKey(Tag.id, ondelete="CASCADE"), nullable=False, index=True)
63
94
  local: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
95
+ relationship_type: Mapped[str] = mapped_column(String(191), nullable=True)
96
+
97
+ event = relationship("Event", back_populates="eventtags", lazy="raise_on_sql", viewonly=True)
98
+ tag = relationship("Tag", back_populates="eventtags", lazy="raise_on_sql", viewonly=True)
@@ -1,4 +1,5 @@
1
1
  from sqlalchemy import Boolean, Integer, String, Text
2
+ from sqlalchemy.orm import relationship
2
3
 
3
4
  from mmisp.db.mypy import Mapped, mapped_column
4
5
  from mmisp.lib.uuid import uuid
@@ -21,3 +22,9 @@ class Galaxy(Base):
21
22
  """must be serialized"""
22
23
  enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
23
24
  local_only: Mapped[bool] = mapped_column(Boolean, default=False)
25
+
26
+ galaxy_clusters = relationship(
27
+ "GalaxyCluster",
28
+ back_populates="galaxy",
29
+ lazy="raise_on_sql",
30
+ ) # type:ignore[assignment,var-annotated]
@@ -1,4 +1,5 @@
1
1
  from sqlalchemy import Boolean, ForeignKey, Integer, String, Text
2
+ from sqlalchemy.orm import relationship
2
3
 
3
4
  from mmisp.db.mypy import Mapped, mapped_column
4
5
  from mmisp.lib.uuid import uuid
@@ -34,6 +35,31 @@ class GalaxyCluster(Base):
34
35
  published: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
35
36
  deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
36
37
 
38
+ org = relationship(
39
+ "Organisation",
40
+ primaryjoin="GalaxyCluster.org_id == Organisation.id",
41
+ back_populates="galaxy_clusters",
42
+ lazy="raise_on_sql",
43
+ foreign_keys="GalaxyCluster.org_id",
44
+ ) # type:ignore[assignment,var-annotated]
45
+ orgc = relationship(
46
+ "Organisation",
47
+ primaryjoin="GalaxyCluster.orgc_id == Organisation.id",
48
+ back_populates="galaxy_clusters_created",
49
+ lazy="raise_on_sql",
50
+ foreign_keys="GalaxyCluster.orgc_id",
51
+ ) # type:ignore[assignment,var-annotated]
52
+ galaxy = relationship(
53
+ "Galaxy",
54
+ back_populates="galaxy_clusters",
55
+ lazy="raise_on_sql",
56
+ ) # type:ignore[assignment,var-annotated]
57
+ galaxy_elements = relationship(
58
+ "GalaxyElement",
59
+ back_populates="galaxy_cluster",
60
+ lazy="raise_on_sql",
61
+ ) # type:ignore[assignment,var-annotated]
62
+
37
63
 
38
64
  class GalaxyElement(Base):
39
65
  __tablename__ = "galaxy_elements"
@@ -45,6 +71,12 @@ class GalaxyElement(Base):
45
71
  key: Mapped[str] = mapped_column(String(255), nullable=False, default="", index=True)
46
72
  value: Mapped[str] = mapped_column(Text, nullable=False)
47
73
 
74
+ galaxy_cluster = relationship(
75
+ "GalaxyCluster",
76
+ back_populates="galaxy_elements",
77
+ lazy="raise_on_sql",
78
+ ) # type:ignore[assignment,var-annotated]
79
+
48
80
 
49
81
  class GalaxyReference(Base):
50
82
  __tablename__ = "galaxy_reference"
@@ -1,4 +1,5 @@
1
1
  from sqlalchemy import Boolean, ForeignKey, Integer, String
2
+ from sqlalchemy.orm import relationship
2
3
 
3
4
  from mmisp.db.database import Base
4
5
  from mmisp.db.mixins import DictMixin
@@ -25,6 +26,19 @@ class Object(Base, DictMixin):
25
26
  first_seen: Mapped[int] = mapped_column(Integer, index=True, default=None)
26
27
  last_seen: Mapped[int] = mapped_column(Integer, index=True, default=None)
27
28
 
29
+ attributes = relationship(
30
+ "Attribute",
31
+ primaryjoin="Object.id == Attribute.object_id",
32
+ back_populates="mispobject",
33
+ lazy="raise_on_sql",
34
+ foreign_keys="Attribute.object_id",
35
+ ) # type:ignore[var-annotated]
36
+ event = relationship(
37
+ "Event",
38
+ back_populates="mispobjects",
39
+ lazy="raise_on_sql",
40
+ ) # type:ignore[var-annotated]
41
+
28
42
 
29
43
  class ObjectTemplate(Base, DictMixin):
30
44
  __tablename__ = "object_templates"
@@ -1,6 +1,7 @@
1
1
  from datetime import datetime
2
2
 
3
3
  from sqlalchemy import Boolean, DateTime, Integer, String, Text
4
+ from sqlalchemy.orm import relationship
4
5
 
5
6
  from mmisp.db.mixins import DictMixin
6
7
  from mmisp.db.mypy import Mapped, mapped_column
@@ -27,3 +28,30 @@ class Organisation(Base, DictMixin):
27
28
  local: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
28
29
  restricted_to_domain: Mapped[str] = mapped_column(Text)
29
30
  landingpage: Mapped[str] = mapped_column(Text)
31
+
32
+ # Relationship to users
33
+ users = relationship("User", back_populates="org", lazy="raise_on_sql")
34
+ creator = relationship(
35
+ "User", primaryjoin="Organisation.created_by == User.id", foreign_keys=created_by, lazy="selectin"
36
+ )
37
+ events = relationship(
38
+ "Event", primaryjoin="Organisation.id == Event.org_id", back_populates="org", lazy="raise_on_sql"
39
+ ) # type:ignore[assignment,var-annotated]
40
+ events_created = relationship(
41
+ "Event", primaryjoin="Organisation.id == Event.orgc_id", back_populates="orgc", lazy="raise_on_sql"
42
+ ) # type:ignore[assignment,var-annotated]
43
+
44
+ galaxy_clusters = relationship(
45
+ "GalaxyCluster",
46
+ primaryjoin="Organisation.id == GalaxyCluster.org_id",
47
+ back_populates="org",
48
+ lazy="raise_on_sql",
49
+ foreign_keys="GalaxyCluster.org_id",
50
+ ) # type:ignore[assignment,var-annotated]
51
+ galaxy_clusters_created = relationship(
52
+ "GalaxyCluster",
53
+ primaryjoin="Organisation.id == GalaxyCluster.orgc_id",
54
+ back_populates="orgc",
55
+ lazy="raise_on_sql",
56
+ foreign_keys="GalaxyCluster.orgc_id",
57
+ ) # type:ignore[assignment,var-annotated]
@@ -1,4 +1,5 @@
1
1
  from sqlalchemy import Boolean, Integer, String
2
+ from sqlalchemy.orm import relationship
2
3
 
3
4
  from mmisp.db.database import Base
4
5
  from mmisp.db.mypy import Mapped, mapped_column
@@ -18,3 +19,6 @@ class Tag(Base):
18
19
  is_galaxy: Mapped[bool] = mapped_column(Boolean, default=False)
19
20
  is_custom_galaxy: Mapped[bool] = mapped_column(Boolean, default=False)
20
21
  local_only: Mapped[bool] = mapped_column(Boolean, default=False)
22
+
23
+ attributetags = relationship("AttributeTag", back_populates="tag", lazy="raise_on_sql", viewonly=True)
24
+ eventtags = relationship("EventTag", back_populates="tag", lazy="raise_on_sql", viewonly=True)
@@ -1,6 +1,7 @@
1
1
  from time import time
2
2
 
3
3
  from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text
4
+ from sqlalchemy.orm import relationship
4
5
 
5
6
  from mmisp.db.mypy import Mapped, mapped_column
6
7
 
@@ -44,3 +45,6 @@ class User(Base):
44
45
  totp: Mapped[str] = mapped_column(String(255))
45
46
  hotp_counter: Mapped[int] = mapped_column(Integer)
46
47
  last_pw_change: Mapped[int] = mapped_column(BigInteger)
48
+
49
+ # Relationships
50
+ org = relationship("Organisation", back_populates="users", lazy="raise_on_sql")
@@ -0,0 +1,60 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from sqlalchemy import and_
4
+
5
+ from mmisp.db.models.attribute import Attribute
6
+
7
+ if TYPE_CHECKING:
8
+ from sqlalchemy import ColumnExpressionArgument
9
+ else:
10
+ ColumnExpressionArgument = any
11
+
12
+
13
+ def get_search_filters(
14
+ value: str | None = None,
15
+ value1: str | None = None,
16
+ value2: str | None = None,
17
+ type: str | None = None,
18
+ category: str | None = None,
19
+ org: str | None = None,
20
+ tags: list[str] | None = None,
21
+ from_: str | None = None,
22
+ to: str | None = None,
23
+ last: int | None = None,
24
+ eventid: str | None = None,
25
+ published: bool | None = None,
26
+ to_ids: bool | None = None,
27
+ deleted: bool | None = None,
28
+ **kwargs,
29
+ ) -> ColumnExpressionArgument:
30
+ cond = [True] # for empty filters
31
+ if value is not None:
32
+ cond.append(Attribute.value == value)
33
+ if value1 is not None:
34
+ cond.append(Attribute.value1 == value1)
35
+ if value2 is not None:
36
+ cond.append(Attribute.value2 == value2)
37
+ if type is not None:
38
+ cond.append(Attribute.type == type)
39
+ if category is not None:
40
+ cond.append(Attribute.category == category)
41
+ if org is not None:
42
+ raise NotImplementedError("filtering by org is currently not implemented")
43
+ if tags is not None:
44
+ raise NotImplementedError("filtering by tags is currently not implemented")
45
+ if from_ is not None:
46
+ raise NotImplementedError("filtering by from is currently not implemented")
47
+ if to is not None:
48
+ raise NotImplementedError("filtering by to is currently not implemented")
49
+ if last is not None:
50
+ raise NotImplementedError("filtering by last is currently not implemented")
51
+ if eventid is not None:
52
+ raise NotImplementedError("filtering by eventid is currently not implemented")
53
+ if published is not None:
54
+ raise NotImplementedError("filtering by published is currently not implemented")
55
+ if to_ids is not None:
56
+ cond.append(Attribute.to_ids == to_ids)
57
+ if deleted is not None:
58
+ cond.append(Attribute.deleted == deleted)
59
+
60
+ return and_(*cond)
@@ -0,0 +1,56 @@
1
+ import httpx
2
+ from deepdiff import DeepDiff
3
+ from icecream import ic
4
+
5
+
6
+ def to_legacy_format(data):
7
+ if isinstance(data, bool):
8
+ return data
9
+ elif isinstance(data, (int, float)):
10
+ return str(data)
11
+ elif isinstance(data, dict):
12
+ return {key: to_legacy_format(value) for key, value in data.items()}
13
+ elif isinstance(data, list):
14
+ return [to_legacy_format(x) for x in data]
15
+ return data
16
+
17
+
18
+ def get_legacy_modern_diff(http_method, path, body, auth_key, client):
19
+ clear_key, auth_key = auth_key
20
+ headers = {"authorization": clear_key, "accept": "application/json"}
21
+
22
+ ic("-" * 50)
23
+ ic(f"Calling {path}")
24
+ ic(body)
25
+
26
+ kwargs = {"headers": headers}
27
+ if http_method != "get":
28
+ kwargs["json"] = body
29
+
30
+ call = getattr(client, http_method)
31
+ response = call(path, **kwargs)
32
+ response_json = response.json()
33
+
34
+ call = getattr(httpx, http_method)
35
+ legacy_response = call(f"http://misp-core{path}", **kwargs)
36
+ ic(legacy_response)
37
+ legacy_response_json = legacy_response.json()
38
+ ic("Modern MISP Response")
39
+ ic(response_json)
40
+ ic("Legacy MISP Response")
41
+ ic(legacy_response_json)
42
+
43
+ diff = DeepDiff(to_legacy_format(response_json), to_legacy_format(legacy_response_json), verbose_level=2)
44
+ ic(diff)
45
+
46
+ # remove None values added in Modern MISP.
47
+ # They shouldnt hurt and removing all None
48
+ # overshoots, MISP is inconsistent when to include what.
49
+ # Note: We don't want the opposite. If MISP includes None, Modern MISP should do this as well!
50
+ diff["dictionary_item_removed"] = {
51
+ k: v for k, v in diff["dictionary_item_removed"].items() if v is not None and v != [] and v != {}
52
+ }
53
+ if diff["dictionary_item_removed"] == {}:
54
+ del diff["dictionary_item_removed"]
55
+
56
+ return diff