cartography 0.93.0rc1__py3-none-any.whl → 0.123.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.
Files changed (822) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +34 -0
  3. cartography/cli.py +903 -225
  4. cartography/client/aws/__init__.py +19 -0
  5. cartography/client/aws/ecr.py +51 -0
  6. cartography/client/core/tx.py +400 -27
  7. cartography/config.py +215 -10
  8. cartography/data/azure_permission_relationships.yaml +20 -0
  9. cartography/data/gcp_permission_relationships.yaml +21 -0
  10. cartography/data/indexes.cypher +1 -200
  11. cartography/data/jobs/analysis/aws_ec2_asset_exposure.json +17 -2
  12. cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
  13. cartography/data/jobs/analysis/gcp_compute_asset_inet_exposure.json +1 -1
  14. cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
  15. cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
  16. cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
  17. cartography/data/jobs/cleanup/github_repos_cleanup.json +27 -0
  18. cartography/data/jobs/scoped_analysis/aws_ec2_iaminstanceprofile.json +15 -0
  19. cartography/data/jobs/scoped_analysis/semgrep_sca_risk_analysis.json +13 -13
  20. cartography/driftdetect/__main__.py +1 -2
  21. cartography/driftdetect/add_shortcut.py +10 -2
  22. cartography/driftdetect/cli.py +72 -75
  23. cartography/driftdetect/detect_deviations.py +7 -3
  24. cartography/driftdetect/get_states.py +20 -8
  25. cartography/driftdetect/model.py +5 -5
  26. cartography/driftdetect/serializers.py +8 -6
  27. cartography/driftdetect/storage.py +2 -2
  28. cartography/graph/cleanupbuilder.py +255 -35
  29. cartography/graph/job.py +104 -20
  30. cartography/graph/querybuilder.py +689 -91
  31. cartography/graph/statement.py +49 -36
  32. cartography/intel/airbyte/__init__.py +105 -0
  33. cartography/intel/airbyte/connections.py +120 -0
  34. cartography/intel/airbyte/destinations.py +81 -0
  35. cartography/intel/airbyte/organizations.py +59 -0
  36. cartography/intel/airbyte/sources.py +78 -0
  37. cartography/intel/airbyte/tags.py +64 -0
  38. cartography/intel/airbyte/users.py +106 -0
  39. cartography/intel/airbyte/util.py +122 -0
  40. cartography/intel/airbyte/workspaces.py +63 -0
  41. cartography/intel/analysis.py +4 -1
  42. cartography/intel/anthropic/__init__.py +62 -0
  43. cartography/intel/anthropic/apikeys.py +72 -0
  44. cartography/intel/anthropic/users.py +75 -0
  45. cartography/intel/anthropic/util.py +51 -0
  46. cartography/intel/anthropic/workspaces.py +95 -0
  47. cartography/intel/aws/__init__.py +137 -59
  48. cartography/intel/aws/acm.py +124 -0
  49. cartography/intel/aws/apigateway.py +482 -217
  50. cartography/intel/aws/apigatewayv2.py +116 -0
  51. cartography/intel/aws/cloudtrail.py +105 -0
  52. cartography/intel/aws/cloudtrail_management_events.py +962 -0
  53. cartography/intel/aws/cloudwatch.py +239 -0
  54. cartography/intel/aws/codebuild.py +132 -0
  55. cartography/intel/aws/cognito.py +201 -0
  56. cartography/intel/aws/config.py +63 -23
  57. cartography/intel/aws/dynamodb.py +108 -40
  58. cartography/intel/aws/ec2/__init__.py +2 -2
  59. cartography/intel/aws/ec2/auto_scaling_groups.py +254 -189
  60. cartography/intel/aws/ec2/elastic_ip_addresses.py +44 -14
  61. cartography/intel/aws/ec2/images.py +74 -39
  62. cartography/intel/aws/ec2/instances.py +262 -137
  63. cartography/intel/aws/ec2/internet_gateways.py +44 -13
  64. cartography/intel/aws/ec2/key_pairs.py +72 -39
  65. cartography/intel/aws/ec2/launch_templates.py +143 -66
  66. cartography/intel/aws/ec2/load_balancer_v2s.py +119 -45
  67. cartography/intel/aws/ec2/load_balancers.py +165 -147
  68. cartography/intel/aws/ec2/network_acls.py +233 -0
  69. cartography/intel/aws/ec2/network_interfaces.py +150 -87
  70. cartography/intel/aws/ec2/reserved_instances.py +48 -17
  71. cartography/intel/aws/ec2/route_tables.py +327 -0
  72. cartography/intel/aws/ec2/security_groups.py +189 -121
  73. cartography/intel/aws/ec2/snapshots.py +93 -91
  74. cartography/intel/aws/ec2/subnets.py +70 -58
  75. cartography/intel/aws/ec2/tgw.py +111 -39
  76. cartography/intel/aws/ec2/util.py +1 -1
  77. cartography/intel/aws/ec2/volumes.py +69 -41
  78. cartography/intel/aws/ec2/vpc.py +157 -116
  79. cartography/intel/aws/ec2/vpc_peerings.py +317 -121
  80. cartography/intel/aws/ecr.py +336 -93
  81. cartography/intel/aws/ecr_image_layers.py +923 -0
  82. cartography/intel/aws/ecs.py +310 -403
  83. cartography/intel/aws/efs.py +261 -0
  84. cartography/intel/aws/eks.py +55 -29
  85. cartography/intel/aws/elasticache.py +130 -83
  86. cartography/intel/aws/elasticsearch.py +70 -24
  87. cartography/intel/aws/emr.py +61 -23
  88. cartography/intel/aws/eventbridge.py +164 -0
  89. cartography/intel/aws/glue.py +181 -0
  90. cartography/intel/aws/guardduty.py +443 -0
  91. cartography/intel/aws/iam.py +978 -464
  92. cartography/intel/aws/iam_instance_profiles.py +73 -0
  93. cartography/intel/aws/identitycenter.py +847 -0
  94. cartography/intel/aws/inspector.py +330 -133
  95. cartography/intel/aws/kms.py +235 -209
  96. cartography/intel/aws/lambda_function.py +328 -176
  97. cartography/intel/aws/organizations.py +40 -19
  98. cartography/intel/aws/permission_relationships.py +144 -68
  99. cartography/intel/aws/rds.py +467 -412
  100. cartography/intel/aws/redshift.py +116 -50
  101. cartography/intel/aws/resourcegroupstaggingapi.py +198 -82
  102. cartography/intel/aws/resources.py +80 -42
  103. cartography/intel/aws/route53.py +419 -318
  104. cartography/intel/aws/s3.py +489 -96
  105. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  106. cartography/intel/aws/secretsmanager.py +217 -40
  107. cartography/intel/aws/securityhub.py +23 -10
  108. cartography/intel/aws/sns.py +226 -0
  109. cartography/intel/aws/sqs.py +74 -96
  110. cartography/intel/aws/ssm.py +142 -33
  111. cartography/intel/aws/util/arns.py +7 -7
  112. cartography/intel/aws/util/common.py +31 -4
  113. cartography/intel/azure/__init__.py +259 -46
  114. cartography/intel/azure/aks.py +175 -0
  115. cartography/intel/azure/app_service.py +105 -0
  116. cartography/intel/azure/compute.py +141 -120
  117. cartography/intel/azure/container_instances.py +95 -0
  118. cartography/intel/azure/cosmosdb.py +706 -519
  119. cartography/intel/azure/data_factory.py +85 -0
  120. cartography/intel/azure/data_factory_dataset.py +128 -0
  121. cartography/intel/azure/data_factory_linked_service.py +119 -0
  122. cartography/intel/azure/data_factory_pipeline.py +142 -0
  123. cartography/intel/azure/data_lake.py +124 -0
  124. cartography/intel/azure/event_grid.py +94 -0
  125. cartography/intel/azure/functions.py +124 -0
  126. cartography/intel/azure/load_balancers.py +263 -0
  127. cartography/intel/azure/logic_apps.py +101 -0
  128. cartography/intel/azure/monitor.py +105 -0
  129. cartography/intel/azure/network.py +467 -0
  130. cartography/intel/azure/permission_relationships.py +466 -0
  131. cartography/intel/azure/rbac.py +309 -0
  132. cartography/intel/azure/resource_groups.py +82 -0
  133. cartography/intel/azure/security_center.py +106 -0
  134. cartography/intel/azure/sql.py +436 -392
  135. cartography/intel/azure/storage.py +467 -335
  136. cartography/intel/azure/subscription.py +49 -55
  137. cartography/intel/azure/tenant.py +46 -28
  138. cartography/intel/azure/util/common.py +13 -0
  139. cartography/intel/azure/util/credentials.py +58 -143
  140. cartography/intel/azure/util/tag.py +41 -0
  141. cartography/intel/bigfix/__init__.py +2 -2
  142. cartography/intel/bigfix/computers.py +93 -65
  143. cartography/intel/cloudflare/__init__.py +74 -0
  144. cartography/intel/cloudflare/accounts.py +57 -0
  145. cartography/intel/cloudflare/dnsrecords.py +64 -0
  146. cartography/intel/cloudflare/members.py +75 -0
  147. cartography/intel/cloudflare/roles.py +65 -0
  148. cartography/intel/cloudflare/zones.py +64 -0
  149. cartography/intel/create_indexes.py +5 -3
  150. cartography/intel/crowdstrike/__init__.py +26 -12
  151. cartography/intel/crowdstrike/endpoints.py +17 -45
  152. cartography/intel/crowdstrike/spotlight.py +13 -5
  153. cartography/intel/cve/__init__.py +91 -26
  154. cartography/intel/cve/feed.py +77 -56
  155. cartography/intel/digitalocean/__init__.py +22 -13
  156. cartography/intel/digitalocean/compute.py +75 -108
  157. cartography/intel/digitalocean/management.py +44 -80
  158. cartography/intel/digitalocean/platform.py +48 -43
  159. cartography/intel/dns.py +41 -12
  160. cartography/intel/duo/__init__.py +21 -16
  161. cartography/intel/duo/api_host.py +14 -9
  162. cartography/intel/duo/endpoints.py +50 -45
  163. cartography/intel/duo/groups.py +18 -14
  164. cartography/intel/duo/phones.py +37 -34
  165. cartography/intel/duo/tokens.py +26 -23
  166. cartography/intel/duo/users.py +54 -50
  167. cartography/intel/duo/web_authn_credentials.py +30 -25
  168. cartography/intel/entra/__init__.py +160 -0
  169. cartography/intel/entra/app_role_assignments.py +284 -0
  170. cartography/intel/entra/applications.py +182 -0
  171. cartography/intel/entra/federation/__init__.py +0 -0
  172. cartography/intel/entra/federation/aws_identity_center.py +77 -0
  173. cartography/intel/entra/groups.py +198 -0
  174. cartography/intel/entra/ou.py +136 -0
  175. cartography/intel/entra/service_principals.py +217 -0
  176. cartography/intel/entra/users.py +259 -0
  177. cartography/intel/gcp/__init__.py +381 -175
  178. cartography/intel/gcp/bigtable_app_profile.py +101 -0
  179. cartography/intel/gcp/bigtable_backup.py +91 -0
  180. cartography/intel/gcp/bigtable_cluster.py +93 -0
  181. cartography/intel/gcp/bigtable_instance.py +86 -0
  182. cartography/intel/gcp/bigtable_table.py +87 -0
  183. cartography/intel/gcp/cai.py +292 -0
  184. cartography/intel/gcp/clients.py +112 -0
  185. cartography/intel/gcp/compute.py +521 -325
  186. cartography/intel/gcp/crm/__init__.py +0 -0
  187. cartography/intel/gcp/crm/folders.py +114 -0
  188. cartography/intel/gcp/crm/orgs.py +70 -0
  189. cartography/intel/gcp/crm/projects.py +120 -0
  190. cartography/intel/gcp/dns.py +134 -179
  191. cartography/intel/gcp/gke.py +100 -107
  192. cartography/intel/gcp/iam.py +262 -0
  193. cartography/intel/gcp/permission_relationships.py +394 -0
  194. cartography/intel/gcp/policy_bindings.py +225 -0
  195. cartography/intel/gcp/storage.py +103 -158
  196. cartography/intel/github/__init__.py +66 -27
  197. cartography/intel/github/commits.py +423 -0
  198. cartography/intel/github/repos.py +871 -160
  199. cartography/intel/github/teams.py +386 -53
  200. cartography/intel/github/users.py +214 -49
  201. cartography/intel/github/util.py +50 -35
  202. cartography/intel/googleworkspace/__init__.py +193 -0
  203. cartography/intel/googleworkspace/devices.py +254 -0
  204. cartography/intel/googleworkspace/groups.py +568 -0
  205. cartography/intel/googleworkspace/oauth_apps.py +259 -0
  206. cartography/intel/googleworkspace/tenant.py +85 -0
  207. cartography/intel/googleworkspace/users.py +138 -0
  208. cartography/intel/gsuite/__init__.py +101 -42
  209. cartography/intel/gsuite/groups.py +291 -0
  210. cartography/intel/gsuite/users.py +142 -0
  211. cartography/intel/jamf/__init__.py +19 -1
  212. cartography/intel/jamf/computers.py +37 -8
  213. cartography/intel/jamf/util.py +7 -2
  214. cartography/intel/kandji/__init__.py +6 -3
  215. cartography/intel/kandji/devices.py +40 -10
  216. cartography/intel/keycloak/__init__.py +153 -0
  217. cartography/intel/keycloak/authenticationexecutions.py +322 -0
  218. cartography/intel/keycloak/authenticationflows.py +77 -0
  219. cartography/intel/keycloak/clients.py +187 -0
  220. cartography/intel/keycloak/groups.py +126 -0
  221. cartography/intel/keycloak/identityproviders.py +94 -0
  222. cartography/intel/keycloak/organizations.py +163 -0
  223. cartography/intel/keycloak/realms.py +61 -0
  224. cartography/intel/keycloak/roles.py +202 -0
  225. cartography/intel/keycloak/scopes.py +73 -0
  226. cartography/intel/keycloak/users.py +70 -0
  227. cartography/intel/keycloak/util.py +47 -0
  228. cartography/intel/kubernetes/__init__.py +60 -14
  229. cartography/intel/kubernetes/clusters.py +86 -0
  230. cartography/intel/kubernetes/eks.py +402 -0
  231. cartography/intel/kubernetes/namespaces.py +60 -55
  232. cartography/intel/kubernetes/pods.py +171 -75
  233. cartography/intel/kubernetes/rbac.py +597 -0
  234. cartography/intel/kubernetes/secrets.py +95 -45
  235. cartography/intel/kubernetes/services.py +131 -63
  236. cartography/intel/kubernetes/util.py +142 -14
  237. cartography/intel/lastpass/__init__.py +2 -2
  238. cartography/intel/lastpass/users.py +23 -12
  239. cartography/intel/oci/__init__.py +44 -11
  240. cartography/intel/oci/iam.py +157 -47
  241. cartography/intel/oci/organizations.py +16 -7
  242. cartography/intel/oci/utils.py +71 -25
  243. cartography/intel/okta/__init__.py +66 -15
  244. cartography/intel/okta/applications.py +57 -25
  245. cartography/intel/okta/awssaml.py +105 -41
  246. cartography/intel/okta/factors.py +19 -5
  247. cartography/intel/okta/groups.py +61 -31
  248. cartography/intel/okta/organization.py +8 -2
  249. cartography/intel/okta/origins.py +9 -3
  250. cartography/intel/okta/roles.py +20 -7
  251. cartography/intel/okta/users.py +31 -10
  252. cartography/intel/okta/utils.py +6 -4
  253. cartography/intel/ontology/__init__.py +44 -0
  254. cartography/intel/ontology/devices.py +54 -0
  255. cartography/intel/ontology/users.py +54 -0
  256. cartography/intel/ontology/utils.py +176 -0
  257. cartography/intel/openai/__init__.py +86 -0
  258. cartography/intel/openai/adminapikeys.py +89 -0
  259. cartography/intel/openai/apikeys.py +96 -0
  260. cartography/intel/openai/projects.py +97 -0
  261. cartography/intel/openai/serviceaccounts.py +82 -0
  262. cartography/intel/openai/users.py +75 -0
  263. cartography/intel/openai/util.py +45 -0
  264. cartography/intel/pagerduty/__init__.py +8 -7
  265. cartography/intel/pagerduty/escalation_policies.py +31 -12
  266. cartography/intel/pagerduty/schedules.py +21 -8
  267. cartography/intel/pagerduty/services.py +18 -7
  268. cartography/intel/pagerduty/teams.py +13 -5
  269. cartography/intel/pagerduty/users.py +6 -2
  270. cartography/intel/pagerduty/vendors.py +6 -2
  271. cartography/intel/scaleway/__init__.py +127 -0
  272. cartography/intel/scaleway/iam/__init__.py +0 -0
  273. cartography/intel/scaleway/iam/apikeys.py +71 -0
  274. cartography/intel/scaleway/iam/applications.py +71 -0
  275. cartography/intel/scaleway/iam/groups.py +71 -0
  276. cartography/intel/scaleway/iam/users.py +71 -0
  277. cartography/intel/scaleway/instances/__init__.py +0 -0
  278. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  279. cartography/intel/scaleway/instances/instances.py +92 -0
  280. cartography/intel/scaleway/projects.py +79 -0
  281. cartography/intel/scaleway/storage/__init__.py +0 -0
  282. cartography/intel/scaleway/storage/snapshots.py +86 -0
  283. cartography/intel/scaleway/storage/volumes.py +84 -0
  284. cartography/intel/scaleway/utils.py +37 -0
  285. cartography/intel/semgrep/__init__.py +30 -5
  286. cartography/intel/semgrep/dependencies.py +255 -0
  287. cartography/intel/semgrep/deployment.py +69 -0
  288. cartography/intel/semgrep/findings.py +157 -117
  289. cartography/intel/sentinelone/__init__.py +75 -0
  290. cartography/intel/sentinelone/account.py +140 -0
  291. cartography/intel/sentinelone/agent.py +139 -0
  292. cartography/intel/sentinelone/api.py +124 -0
  293. cartography/intel/sentinelone/application.py +248 -0
  294. cartography/intel/sentinelone/cve.py +119 -0
  295. cartography/intel/sentinelone/utils.py +28 -0
  296. cartography/intel/slack/__init__.py +78 -0
  297. cartography/intel/slack/channels.py +80 -0
  298. cartography/intel/slack/groups.py +90 -0
  299. cartography/intel/slack/teams.py +65 -0
  300. cartography/intel/slack/users.py +57 -0
  301. cartography/intel/slack/utils.py +29 -0
  302. cartography/intel/snipeit/__init__.py +44 -0
  303. cartography/intel/snipeit/asset.py +80 -0
  304. cartography/intel/snipeit/user.py +78 -0
  305. cartography/intel/snipeit/util.py +40 -0
  306. cartography/intel/spacelift/__init__.py +161 -0
  307. cartography/intel/spacelift/account.py +73 -0
  308. cartography/intel/spacelift/ec2_ownership.py +280 -0
  309. cartography/intel/spacelift/runs.py +463 -0
  310. cartography/intel/spacelift/spaces.py +112 -0
  311. cartography/intel/spacelift/stacks.py +119 -0
  312. cartography/intel/spacelift/util.py +122 -0
  313. cartography/intel/spacelift/workerpools.py +131 -0
  314. cartography/intel/spacelift/workers.py +128 -0
  315. cartography/intel/tailscale/__init__.py +77 -0
  316. cartography/intel/tailscale/acls.py +146 -0
  317. cartography/intel/tailscale/devices.py +127 -0
  318. cartography/intel/tailscale/postureintegrations.py +81 -0
  319. cartography/intel/tailscale/tailnets.py +76 -0
  320. cartography/intel/tailscale/users.py +80 -0
  321. cartography/intel/tailscale/utils.py +132 -0
  322. cartography/intel/trivy/__init__.py +272 -0
  323. cartography/intel/trivy/scanner.py +386 -0
  324. cartography/models/airbyte/__init__.py +0 -0
  325. cartography/models/airbyte/connection.py +138 -0
  326. cartography/models/airbyte/destination.py +75 -0
  327. cartography/models/airbyte/organization.py +19 -0
  328. cartography/models/airbyte/source.py +75 -0
  329. cartography/models/airbyte/stream.py +74 -0
  330. cartography/models/airbyte/tag.py +69 -0
  331. cartography/models/airbyte/user.py +115 -0
  332. cartography/models/airbyte/workspace.py +46 -0
  333. cartography/models/anthropic/__init__.py +0 -0
  334. cartography/models/anthropic/apikey.py +94 -0
  335. cartography/models/anthropic/organization.py +19 -0
  336. cartography/models/anthropic/user.py +52 -0
  337. cartography/models/anthropic/workspace.py +90 -0
  338. cartography/models/aws/acm/__init__.py +0 -0
  339. cartography/models/aws/acm/certificate.py +75 -0
  340. cartography/models/aws/apigateway/__init__.py +0 -0
  341. cartography/models/aws/apigateway/apigateway.py +51 -0
  342. cartography/models/aws/apigateway/apigatewaycertificate.py +72 -0
  343. cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
  344. cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
  345. cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
  346. cartography/models/aws/apigateway/apigatewayresource.py +70 -0
  347. cartography/models/aws/apigateway/apigatewaystage.py +75 -0
  348. cartography/models/aws/apigatewayv2/__init__.py +0 -0
  349. cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
  350. cartography/models/aws/cloudtrail/__init__.py +0 -0
  351. cartography/models/aws/cloudtrail/management_events.py +153 -0
  352. cartography/models/aws/cloudtrail/trail.py +106 -0
  353. cartography/models/aws/cloudwatch/__init__.py +0 -0
  354. cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
  355. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  356. cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
  357. cartography/models/aws/codebuild/__init__.py +0 -0
  358. cartography/models/aws/codebuild/project.py +49 -0
  359. cartography/models/aws/cognito/__init__.py +0 -0
  360. cartography/models/aws/cognito/identity_pool.py +70 -0
  361. cartography/models/aws/cognito/user_pool.py +47 -0
  362. cartography/models/aws/dynamodb/gsi.py +30 -22
  363. cartography/models/aws/dynamodb/tables.py +27 -17
  364. cartography/models/aws/ec2/auto_scaling_groups.py +224 -0
  365. cartography/models/aws/ec2/images.py +36 -34
  366. cartography/models/aws/ec2/instances.py +85 -38
  367. cartography/models/aws/ec2/keypair.py +59 -0
  368. cartography/models/aws/ec2/keypair_instance.py +76 -0
  369. cartography/models/aws/ec2/launch_configurations.py +59 -0
  370. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  371. cartography/models/aws/ec2/launch_templates.py +21 -17
  372. cartography/models/aws/ec2/load_balancer_listeners.py +72 -0
  373. cartography/models/aws/ec2/load_balancers.py +112 -0
  374. cartography/models/aws/ec2/network_acl_rules.py +106 -0
  375. cartography/models/aws/ec2/network_acls.py +95 -0
  376. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  377. cartography/models/aws/ec2/networkinterfaces.py +57 -37
  378. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  379. cartography/models/aws/ec2/reservations.py +18 -14
  380. cartography/models/aws/ec2/route_table_associations.py +97 -0
  381. cartography/models/aws/ec2/route_tables.py +128 -0
  382. cartography/models/aws/ec2/routes.py +85 -0
  383. cartography/models/aws/ec2/security_group_rules.py +109 -0
  384. cartography/models/aws/ec2/security_groups.py +90 -0
  385. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  386. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  387. cartography/models/aws/ec2/snapshots.py +58 -0
  388. cartography/models/aws/ec2/subnet_instance.py +26 -19
  389. cartography/models/aws/ec2/subnet_networkinterface.py +42 -31
  390. cartography/models/aws/ec2/subnets.py +65 -0
  391. cartography/models/aws/ec2/volumes.py +67 -40
  392. cartography/models/aws/ec2/vpc.py +46 -0
  393. cartography/models/aws/ec2/vpc_cidr.py +102 -0
  394. cartography/models/aws/ec2/vpc_peering.py +157 -0
  395. cartography/models/aws/ecr/__init__.py +0 -0
  396. cartography/models/aws/ecr/image.py +146 -0
  397. cartography/models/aws/ecr/image_layer.py +107 -0
  398. cartography/models/aws/ecr/repository.py +72 -0
  399. cartography/models/aws/ecr/repository_image.py +95 -0
  400. cartography/models/aws/ecs/__init__.py +0 -0
  401. cartography/models/aws/ecs/clusters.py +64 -0
  402. cartography/models/aws/ecs/container_definitions.py +93 -0
  403. cartography/models/aws/ecs/container_instances.py +84 -0
  404. cartography/models/aws/ecs/containers.py +101 -0
  405. cartography/models/aws/ecs/services.py +134 -0
  406. cartography/models/aws/ecs/task_definitions.py +135 -0
  407. cartography/models/aws/ecs/tasks.py +134 -0
  408. cartography/models/aws/efs/__init__.py +0 -0
  409. cartography/models/aws/efs/access_point.py +77 -0
  410. cartography/models/aws/efs/file_system.py +60 -0
  411. cartography/models/aws/efs/mount_target.py +79 -0
  412. cartography/models/aws/eks/clusters.py +23 -21
  413. cartography/models/aws/elasticache/__init__.py +0 -0
  414. cartography/models/aws/elasticache/cluster.py +65 -0
  415. cartography/models/aws/elasticache/topic.py +67 -0
  416. cartography/models/aws/emr.py +32 -30
  417. cartography/models/aws/eventbridge/__init__.py +0 -0
  418. cartography/models/aws/eventbridge/rule.py +77 -0
  419. cartography/models/aws/eventbridge/target.py +71 -0
  420. cartography/models/aws/glue/__init__.py +0 -0
  421. cartography/models/aws/glue/connection.py +51 -0
  422. cartography/models/aws/glue/job.py +69 -0
  423. cartography/models/aws/guardduty/__init__.py +1 -0
  424. cartography/models/aws/guardduty/detectors.py +50 -0
  425. cartography/models/aws/guardduty/findings.py +121 -0
  426. cartography/models/aws/iam/__init__.py +0 -0
  427. cartography/models/aws/iam/access_key.py +103 -0
  428. cartography/models/aws/iam/account_role.py +24 -0
  429. cartography/models/aws/iam/federated_principal.py +60 -0
  430. cartography/models/aws/iam/group.py +60 -0
  431. cartography/models/aws/iam/group_membership.py +27 -0
  432. cartography/models/aws/iam/inline_policy.py +78 -0
  433. cartography/models/aws/iam/instanceprofile.py +76 -0
  434. cartography/models/aws/iam/managed_policy.py +51 -0
  435. cartography/models/aws/iam/policy_statement.py +57 -0
  436. cartography/models/aws/iam/role.py +83 -0
  437. cartography/models/aws/iam/root_principal.py +52 -0
  438. cartography/models/aws/iam/service_principal.py +30 -0
  439. cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
  440. cartography/models/aws/iam/user.py +59 -0
  441. cartography/models/aws/identitycenter/__init__.py +0 -0
  442. cartography/models/aws/identitycenter/awsidentitycenter.py +49 -0
  443. cartography/models/aws/identitycenter/awspermissionset.py +162 -0
  444. cartography/models/aws/identitycenter/awssogroup.py +70 -0
  445. cartography/models/aws/identitycenter/awsssouser.py +110 -0
  446. cartography/models/aws/inspector/findings.py +124 -58
  447. cartography/models/aws/inspector/packages.py +18 -42
  448. cartography/models/aws/kms/__init__.py +0 -0
  449. cartography/models/aws/kms/aliases.py +86 -0
  450. cartography/models/aws/kms/grants.py +65 -0
  451. cartography/models/aws/kms/keys.py +88 -0
  452. cartography/models/aws/lambda_function/__init__.py +0 -0
  453. cartography/models/aws/lambda_function/alias.py +74 -0
  454. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  455. cartography/models/aws/lambda_function/lambda_function.py +91 -0
  456. cartography/models/aws/lambda_function/layer.py +72 -0
  457. cartography/models/aws/rds/__init__.py +0 -0
  458. cartography/models/aws/rds/cluster.py +91 -0
  459. cartography/models/aws/rds/event_subscription.py +146 -0
  460. cartography/models/aws/rds/instance.py +156 -0
  461. cartography/models/aws/rds/snapshot.py +108 -0
  462. cartography/models/aws/rds/subnet_group.py +101 -0
  463. cartography/models/aws/route53/__init__.py +0 -0
  464. cartography/models/aws/route53/dnsrecord.py +235 -0
  465. cartography/models/aws/route53/nameserver.py +63 -0
  466. cartography/models/aws/route53/subzone.py +40 -0
  467. cartography/models/aws/route53/zone.py +47 -0
  468. cartography/models/aws/s3/__init__.py +0 -0
  469. cartography/models/aws/s3/account_public_access_block.py +51 -0
  470. cartography/models/aws/s3/notification.py +24 -0
  471. cartography/models/aws/secretsmanager/__init__.py +0 -0
  472. cartography/models/aws/secretsmanager/secret.py +106 -0
  473. cartography/models/aws/secretsmanager/secret_version.py +114 -0
  474. cartography/models/aws/sns/__init__.py +0 -0
  475. cartography/models/aws/sns/topic.py +50 -0
  476. cartography/models/aws/sns/topic_subscription.py +74 -0
  477. cartography/models/aws/sqs/__init__.py +0 -0
  478. cartography/models/aws/sqs/queue.py +89 -0
  479. cartography/models/aws/ssm/instance_information.py +51 -39
  480. cartography/models/aws/ssm/instance_patch.py +32 -26
  481. cartography/models/aws/ssm/parameters.py +84 -0
  482. cartography/models/azure/__init__.py +0 -0
  483. cartography/models/azure/aks_cluster.py +54 -0
  484. cartography/models/azure/aks_nodepool.py +54 -0
  485. cartography/models/azure/app_service.py +59 -0
  486. cartography/models/azure/container_instance.py +57 -0
  487. cartography/models/azure/cosmosdb/__init__.py +0 -0
  488. cartography/models/azure/cosmosdb/account.py +77 -0
  489. cartography/models/azure/cosmosdb/accountfailoverpolicy.py +77 -0
  490. cartography/models/azure/cosmosdb/cassandrakeyspace.py +82 -0
  491. cartography/models/azure/cosmosdb/cassandratable.py +81 -0
  492. cartography/models/azure/cosmosdb/corspolicy.py +74 -0
  493. cartography/models/azure/cosmosdb/dblocation.py +120 -0
  494. cartography/models/azure/cosmosdb/mongodbcollection.py +82 -0
  495. cartography/models/azure/cosmosdb/mongodbdatabase.py +78 -0
  496. cartography/models/azure/cosmosdb/privateendpointconnection.py +81 -0
  497. cartography/models/azure/cosmosdb/sqlcontainer.py +88 -0
  498. cartography/models/azure/cosmosdb/sqldatabase.py +78 -0
  499. cartography/models/azure/cosmosdb/tableresource.py +76 -0
  500. cartography/models/azure/cosmosdb/virtualnetworkrule.py +78 -0
  501. cartography/models/azure/data_factory/__init__.py +0 -0
  502. cartography/models/azure/data_factory/data_factory.py +51 -0
  503. cartography/models/azure/data_factory/data_factory_dataset.py +94 -0
  504. cartography/models/azure/data_factory/data_factory_linked_service.py +78 -0
  505. cartography/models/azure/data_factory/data_factory_pipeline.py +93 -0
  506. cartography/models/azure/data_lake_filesystem.py +51 -0
  507. cartography/models/azure/event_grid_topic.py +57 -0
  508. cartography/models/azure/function_app.py +59 -0
  509. cartography/models/azure/load_balancer/__init__.py +0 -0
  510. cartography/models/azure/load_balancer/load_balancer.py +49 -0
  511. cartography/models/azure/load_balancer/load_balancer_backend_pool.py +73 -0
  512. cartography/models/azure/load_balancer/load_balancer_frontend_ip.py +75 -0
  513. cartography/models/azure/load_balancer/load_balancer_inbound_nat_rule.py +78 -0
  514. cartography/models/azure/load_balancer/load_balancer_rule.py +108 -0
  515. cartography/models/azure/logic_apps.py +56 -0
  516. cartography/models/azure/monitor.py +54 -0
  517. cartography/models/azure/network_interface.py +112 -0
  518. cartography/models/azure/network_security_group.py +50 -0
  519. cartography/models/azure/permission_relationships.py +60 -0
  520. cartography/models/azure/principal.py +41 -0
  521. cartography/models/azure/public_ip_address.py +50 -0
  522. cartography/models/azure/rbac.py +268 -0
  523. cartography/models/azure/resource_groups.py +52 -0
  524. cartography/models/azure/security_center.py +50 -0
  525. cartography/models/azure/sql/__init__.py +0 -0
  526. cartography/models/azure/sql/databasethreatdetectionpolicy.py +85 -0
  527. cartography/models/azure/sql/elasticpool.py +77 -0
  528. cartography/models/azure/sql/failovergroup.py +73 -0
  529. cartography/models/azure/sql/recoverabledatabase.py +75 -0
  530. cartography/models/azure/sql/replicationlink.py +81 -0
  531. cartography/models/azure/sql/restorabledroppeddatabase.py +82 -0
  532. cartography/models/azure/sql/restorepoint.py +74 -0
  533. cartography/models/azure/sql/serveradadministrator.py +74 -0
  534. cartography/models/azure/sql/serverdnsalias.py +71 -0
  535. cartography/models/azure/sql/sqldatabase.py +85 -0
  536. cartography/models/azure/sql/sqlserver.py +50 -0
  537. cartography/models/azure/sql/transparentdataencryption.py +76 -0
  538. cartography/models/azure/storage/__init__.py +0 -0
  539. cartography/models/azure/storage/account.py +59 -0
  540. cartography/models/azure/storage/blobcontainer.py +85 -0
  541. cartography/models/azure/storage/blobservice.py +71 -0
  542. cartography/models/azure/storage/fileservice.py +71 -0
  543. cartography/models/azure/storage/fileshare.py +82 -0
  544. cartography/models/azure/storage/queue.py +71 -0
  545. cartography/models/azure/storage/queueservice.py +73 -0
  546. cartography/models/azure/storage/table.py +72 -0
  547. cartography/models/azure/storage/tableservice.py +73 -0
  548. cartography/models/azure/subnet.py +101 -0
  549. cartography/models/azure/subscription.py +47 -0
  550. cartography/models/azure/tags/__init__.py +0 -0
  551. cartography/models/azure/tags/storage_tag.py +40 -0
  552. cartography/models/azure/tags/tag.py +37 -0
  553. cartography/models/azure/tenant.py +17 -0
  554. cartography/models/azure/virtual_network.py +49 -0
  555. cartography/models/azure/vm/__init__.py +0 -0
  556. cartography/models/azure/vm/datadisk.py +80 -0
  557. cartography/models/azure/vm/disk.py +55 -0
  558. cartography/models/azure/vm/snapshot.py +56 -0
  559. cartography/models/azure/vm/virtualmachine.py +59 -0
  560. cartography/models/bigfix/bigfix_computer.py +42 -38
  561. cartography/models/bigfix/bigfix_root.py +3 -3
  562. cartography/models/cloudflare/__init__.py +0 -0
  563. cartography/models/cloudflare/account.py +25 -0
  564. cartography/models/cloudflare/dnsrecord.py +55 -0
  565. cartography/models/cloudflare/member.py +86 -0
  566. cartography/models/cloudflare/role.py +44 -0
  567. cartography/models/cloudflare/zone.py +59 -0
  568. cartography/models/core/common.py +53 -2
  569. cartography/models/core/nodes.py +20 -4
  570. cartography/models/core/relationships.py +58 -6
  571. cartography/models/crowdstrike/__init__.py +0 -0
  572. cartography/models/crowdstrike/hosts.py +51 -0
  573. cartography/models/cve/cve.py +34 -32
  574. cartography/models/cve/cve_feed.py +6 -6
  575. cartography/models/digitalocean/__init__.py +0 -0
  576. cartography/models/digitalocean/account.py +21 -0
  577. cartography/models/digitalocean/droplet.py +58 -0
  578. cartography/models/digitalocean/project.py +48 -0
  579. cartography/models/duo/api_host.py +3 -3
  580. cartography/models/duo/endpoint.py +43 -41
  581. cartography/models/duo/group.py +14 -14
  582. cartography/models/duo/phone.py +27 -27
  583. cartography/models/duo/token.py +16 -16
  584. cartography/models/duo/user.py +50 -44
  585. cartography/models/duo/web_authn_credential.py +27 -19
  586. cartography/models/entra/__init__.py +0 -0
  587. cartography/models/entra/app_role_assignment.py +115 -0
  588. cartography/models/entra/application.py +49 -0
  589. cartography/models/entra/entra_user_to_aws_sso.py +41 -0
  590. cartography/models/entra/group.py +117 -0
  591. cartography/models/entra/ou.py +48 -0
  592. cartography/models/entra/service_principal.py +104 -0
  593. cartography/models/entra/tenant.py +39 -0
  594. cartography/models/entra/user.py +90 -0
  595. cartography/models/gcp/__init__.py +0 -0
  596. cartography/models/gcp/bigtable/__init__.py +0 -0
  597. cartography/models/gcp/bigtable/app_profile.py +94 -0
  598. cartography/models/gcp/bigtable/backup.py +91 -0
  599. cartography/models/gcp/bigtable/cluster.py +73 -0
  600. cartography/models/gcp/bigtable/instance.py +52 -0
  601. cartography/models/gcp/bigtable/table.py +69 -0
  602. cartography/models/gcp/compute/__init__.py +0 -0
  603. cartography/models/gcp/compute/subnet.py +74 -0
  604. cartography/models/gcp/compute/vpc.py +50 -0
  605. cartography/models/gcp/crm/__init__.py +0 -0
  606. cartography/models/gcp/crm/folders.py +98 -0
  607. cartography/models/gcp/crm/organizations.py +21 -0
  608. cartography/models/gcp/crm/projects.py +100 -0
  609. cartography/models/gcp/dns.py +109 -0
  610. cartography/models/gcp/gke.py +69 -0
  611. cartography/models/gcp/iam.py +73 -0
  612. cartography/models/gcp/permission_relationships.py +61 -0
  613. cartography/models/gcp/policy_bindings.py +93 -0
  614. cartography/models/gcp/storage/__init__.py +0 -0
  615. cartography/models/gcp/storage/bucket.py +119 -0
  616. cartography/models/github/commits.py +63 -0
  617. cartography/models/github/dependencies.py +73 -0
  618. cartography/models/github/manifests.py +49 -0
  619. cartography/models/github/orgs.py +27 -0
  620. cartography/models/github/teams.py +74 -22
  621. cartography/models/github/users.py +149 -0
  622. cartography/models/googleworkspace/__init__.py +0 -0
  623. cartography/models/googleworkspace/device.py +132 -0
  624. cartography/models/googleworkspace/group.py +382 -0
  625. cartography/models/googleworkspace/oauth_app.py +124 -0
  626. cartography/models/googleworkspace/tenant.py +30 -0
  627. cartography/models/googleworkspace/user.py +113 -0
  628. cartography/models/gsuite/__init__.py +0 -0
  629. cartography/models/gsuite/group.py +218 -0
  630. cartography/models/gsuite/tenant.py +29 -0
  631. cartography/models/gsuite/user.py +107 -0
  632. cartography/models/kandji/device.py +22 -17
  633. cartography/models/kandji/tenant.py +6 -4
  634. cartography/models/keycloak/__init__.py +0 -0
  635. cartography/models/keycloak/authenticationexecution.py +160 -0
  636. cartography/models/keycloak/authenticationflow.py +54 -0
  637. cartography/models/keycloak/client.py +179 -0
  638. cartography/models/keycloak/group.py +101 -0
  639. cartography/models/keycloak/identityprovider.py +89 -0
  640. cartography/models/keycloak/organization.py +116 -0
  641. cartography/models/keycloak/organizationdomain.py +73 -0
  642. cartography/models/keycloak/realm.py +173 -0
  643. cartography/models/keycloak/role.py +126 -0
  644. cartography/models/keycloak/scope.py +73 -0
  645. cartography/models/keycloak/user.py +55 -0
  646. cartography/models/kubernetes/__init__.py +0 -0
  647. cartography/models/kubernetes/clusterrolebindings.py +138 -0
  648. cartography/models/kubernetes/clusterroles.py +52 -0
  649. cartography/models/kubernetes/clusters.py +26 -0
  650. cartography/models/kubernetes/containers.py +133 -0
  651. cartography/models/kubernetes/groups.py +107 -0
  652. cartography/models/kubernetes/namespaces.py +51 -0
  653. cartography/models/kubernetes/oidc.py +51 -0
  654. cartography/models/kubernetes/pods.py +80 -0
  655. cartography/models/kubernetes/rolebindings.py +159 -0
  656. cartography/models/kubernetes/roles.py +76 -0
  657. cartography/models/kubernetes/secrets.py +79 -0
  658. cartography/models/kubernetes/serviceaccounts.py +77 -0
  659. cartography/models/kubernetes/services.py +108 -0
  660. cartography/models/kubernetes/users.py +105 -0
  661. cartography/models/lastpass/tenant.py +3 -3
  662. cartography/models/lastpass/user.py +36 -28
  663. cartography/models/ontology/__init__.py +0 -0
  664. cartography/models/ontology/device.py +137 -0
  665. cartography/models/ontology/mapping/__init__.py +76 -0
  666. cartography/models/ontology/mapping/data/__init__.py +0 -0
  667. cartography/models/ontology/mapping/data/apikeys.py +93 -0
  668. cartography/models/ontology/mapping/data/computeinstance.py +95 -0
  669. cartography/models/ontology/mapping/data/containers.py +88 -0
  670. cartography/models/ontology/mapping/data/databases.py +182 -0
  671. cartography/models/ontology/mapping/data/devices.py +194 -0
  672. cartography/models/ontology/mapping/data/thirdpartyapps.py +140 -0
  673. cartography/models/ontology/mapping/data/useraccounts.py +416 -0
  674. cartography/models/ontology/mapping/data/users.py +63 -0
  675. cartography/models/ontology/mapping/specs.py +85 -0
  676. cartography/models/ontology/user.py +51 -0
  677. cartography/models/openai/__init__.py +0 -0
  678. cartography/models/openai/adminapikey.py +94 -0
  679. cartography/models/openai/apikey.py +88 -0
  680. cartography/models/openai/organization.py +17 -0
  681. cartography/models/openai/project.py +89 -0
  682. cartography/models/openai/serviceaccount.py +50 -0
  683. cartography/models/openai/user.py +53 -0
  684. cartography/models/scaleway/__init__.py +0 -0
  685. cartography/models/scaleway/iam/__init__.py +0 -0
  686. cartography/models/scaleway/iam/apikey.py +100 -0
  687. cartography/models/scaleway/iam/application.py +52 -0
  688. cartography/models/scaleway/iam/group.py +95 -0
  689. cartography/models/scaleway/iam/user.py +64 -0
  690. cartography/models/scaleway/instance/__init__.py +0 -0
  691. cartography/models/scaleway/instance/flexibleip.py +52 -0
  692. cartography/models/scaleway/instance/instance.py +120 -0
  693. cartography/models/scaleway/organization.py +19 -0
  694. cartography/models/scaleway/project.py +48 -0
  695. cartography/models/scaleway/storage/__init__.py +0 -0
  696. cartography/models/scaleway/storage/snapshot.py +78 -0
  697. cartography/models/scaleway/storage/volume.py +51 -0
  698. cartography/models/semgrep/dependencies.py +102 -0
  699. cartography/models/semgrep/deployment.py +5 -5
  700. cartography/models/semgrep/findings.py +58 -40
  701. cartography/models/semgrep/locations.py +27 -21
  702. cartography/models/sentinelone/__init__.py +1 -0
  703. cartography/models/sentinelone/account.py +40 -0
  704. cartography/models/sentinelone/agent.py +50 -0
  705. cartography/models/sentinelone/application.py +44 -0
  706. cartography/models/sentinelone/application_version.py +96 -0
  707. cartography/models/sentinelone/cve.py +73 -0
  708. cartography/models/slack/__init__.py +0 -0
  709. cartography/models/slack/channels.py +92 -0
  710. cartography/models/slack/group.py +129 -0
  711. cartography/models/slack/team.py +22 -0
  712. cartography/models/slack/user.py +62 -0
  713. cartography/models/snipeit/__init__.py +0 -0
  714. cartography/models/snipeit/asset.py +92 -0
  715. cartography/models/snipeit/tenant.py +19 -0
  716. cartography/models/snipeit/user.py +60 -0
  717. cartography/models/spacelift/__init__.py +0 -0
  718. cartography/models/spacelift/cloudtrailevent.py +120 -0
  719. cartography/models/spacelift/run.py +162 -0
  720. cartography/models/spacelift/space.py +131 -0
  721. cartography/models/spacelift/spaceliftaccount.py +31 -0
  722. cartography/models/spacelift/spaceliftgitcommit.py +157 -0
  723. cartography/models/spacelift/stack.py +96 -0
  724. cartography/models/spacelift/user.py +63 -0
  725. cartography/models/spacelift/worker.py +97 -0
  726. cartography/models/spacelift/workerpool.py +90 -0
  727. cartography/models/tailscale/__init__.py +0 -0
  728. cartography/models/tailscale/device.py +96 -0
  729. cartography/models/tailscale/group.py +86 -0
  730. cartography/models/tailscale/postureintegration.py +58 -0
  731. cartography/models/tailscale/tag.py +102 -0
  732. cartography/models/tailscale/tailnet.py +29 -0
  733. cartography/models/tailscale/user.py +57 -0
  734. cartography/models/trivy/__init__.py +0 -0
  735. cartography/models/trivy/findings.py +66 -0
  736. cartography/models/trivy/fix.py +66 -0
  737. cartography/models/trivy/package.py +71 -0
  738. cartography/rules/README.md +1 -0
  739. cartography/rules/__init__.py +0 -0
  740. cartography/rules/cli.py +261 -0
  741. cartography/rules/data/__init__.py +0 -0
  742. cartography/rules/data/rules/__init__.py +46 -0
  743. cartography/rules/data/rules/cloud_security_product_deactivated.py +49 -0
  744. cartography/rules/data/rules/compute_instance_exposed.py +51 -0
  745. cartography/rules/data/rules/database_instance_exposed.py +53 -0
  746. cartography/rules/data/rules/delegation_boundary_modifiable.py +90 -0
  747. cartography/rules/data/rules/identity_administration_privileges.py +100 -0
  748. cartography/rules/data/rules/inactive_user_active_accounts.py +48 -0
  749. cartography/rules/data/rules/malicious_npm_dependencies_shai_hulud.py +2222 -0
  750. cartography/rules/data/rules/mfa_missing.py +46 -0
  751. cartography/rules/data/rules/object_storage_public.py +100 -0
  752. cartography/rules/data/rules/policy_administration_privileges.py +104 -0
  753. cartography/rules/data/rules/unmanaged_accounts.py +43 -0
  754. cartography/rules/data/rules/workload_identity_admin_capabilities.py +193 -0
  755. cartography/rules/formatters.py +108 -0
  756. cartography/rules/runners.py +216 -0
  757. cartography/rules/spec/__init__.py +0 -0
  758. cartography/rules/spec/model.py +267 -0
  759. cartography/rules/spec/result.py +38 -0
  760. cartography/stats.py +4 -4
  761. cartography/sync.py +137 -31
  762. cartography/util.py +187 -77
  763. cartography-0.123.0.dist-info/METADATA +230 -0
  764. cartography-0.123.0.dist-info/RECORD +856 -0
  765. {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/WHEEL +1 -1
  766. {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
  767. {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info/licenses}/LICENSE +1 -1
  768. cartography/data/jobs/analysis/aws_ec2_iaminstance.json +0 -10
  769. cartography/data/jobs/analysis/aws_ec2_iaminstanceprofile.json +0 -10
  770. cartography/data/jobs/cleanup/aws_apigateway_details.json +0 -10
  771. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  772. cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
  773. cartography/data/jobs/cleanup/aws_import_apigateway_cleanup.json +0 -45
  774. cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json +0 -24
  775. cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
  776. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  777. cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
  778. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  779. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  780. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  781. cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
  782. cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
  783. cartography/data/jobs/cleanup/aws_import_snapshots_cleanup.json +0 -30
  784. cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
  785. cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
  786. cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
  787. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  788. cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json +0 -25
  789. cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json +0 -15
  790. cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json +0 -25
  791. cartography/data/jobs/cleanup/azure_cosmosdb_sql_database_cleanup.json +0 -25
  792. cartography/data/jobs/cleanup/azure_cosmosdb_table_resources_cleanup.json +0 -15
  793. cartography/data/jobs/cleanup/azure_database_account_cleanup.json +0 -85
  794. cartography/data/jobs/cleanup/azure_import_disks_cleanup.json +0 -15
  795. cartography/data/jobs/cleanup/azure_import_snapshots_cleanup.json +0 -15
  796. cartography/data/jobs/cleanup/azure_import_virtual_machines_cleanup.json +0 -25
  797. cartography/data/jobs/cleanup/azure_sql_server_cleanup.json +0 -125
  798. cartography/data/jobs/cleanup/azure_storage_account_cleanup.json +0 -95
  799. cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json +0 -14
  800. cartography/data/jobs/cleanup/azure_tenant_cleanup.json +0 -9
  801. cartography/data/jobs/cleanup/crxcavator_import_cleanup.json +0 -18
  802. cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
  803. cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
  804. cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
  805. cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
  806. cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
  807. cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
  808. cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
  809. cartography/data/jobs/cleanup/github_users_cleanup.json +0 -23
  810. cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
  811. cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
  812. cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
  813. cartography/intel/crxcavator/__init__.py +0 -44
  814. cartography/intel/crxcavator/crxcavator.py +0 -329
  815. cartography/intel/gcp/crm.py +0 -302
  816. cartography/intel/gsuite/api.py +0 -284
  817. cartography/models/aws/ec2/keypairs.py +0 -64
  818. cartography-0.93.0rc1.dist-info/METADATA +0 -55
  819. cartography-0.93.0rc1.dist-info/NOTICE +0 -4
  820. cartography-0.93.0rc1.dist-info/RECORD +0 -341
  821. /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
  822. {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,8 @@
1
1
  import logging
2
2
  from dataclasses import asdict
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version
3
5
  from string import Template
4
- from typing import Dict
5
- from typing import List
6
- from typing import Optional
7
- from typing import Set
8
- from typing import Tuple
9
6
 
10
7
  from cartography.models.core.common import PropertyRef
11
8
  from cartography.models.core.nodes import CartographyNodeProperties
@@ -14,21 +11,319 @@ from cartography.models.core.nodes import ExtraNodeLabels
14
11
  from cartography.models.core.relationships import CartographyRelSchema
15
12
  from cartography.models.core.relationships import LinkDirection
16
13
  from cartography.models.core.relationships import OtherRelationships
14
+ from cartography.models.core.relationships import SourceNodeMatcher
17
15
  from cartography.models.core.relationships import TargetNodeMatcher
16
+ from cartography.models.ontology.mapping import (
17
+ get_semantic_label_mapping_from_node_schema,
18
+ )
19
+ from cartography.models.ontology.mapping.specs import OntologyFieldMapping
18
20
 
19
21
  logger = logging.getLogger(__name__)
20
22
 
21
23
 
24
+ def _build_ontology_field_statement_invert_boolean(
25
+ mapping_field: OntologyFieldMapping,
26
+ property_ref: PropertyRef,
27
+ ) -> str:
28
+ # toBooleanOrNull will return a boolean or null if it can't be converted
29
+ # coalesce will return the first non-null value, so if toBooleanOrNull returns null,
30
+ # we invert the boolean value of the property_ref existence
31
+ # ex: "false", "0", "no" => false; anything else => true; null/absent => true
32
+ invert_boolean_template = Template(
33
+ "i.$node_property = (NOT(coalesce(toBooleanOrNull($property_ref), false)))"
34
+ )
35
+ return invert_boolean_template.safe_substitute(
36
+ node_property=f"_ont_{mapping_field.ontology_field}",
37
+ property_ref=property_ref,
38
+ )
39
+
40
+
41
+ def _build_ontology_field_statement_to_boolean(
42
+ mapping_field: OntologyFieldMapping,
43
+ property_ref: PropertyRef,
44
+ ) -> str:
45
+ # toBoleanOrNull will return a boolean or null if it can't be converted
46
+ # coalesce will return the first non-null value, so if toBooleanOrNull returns null,
47
+ # it will return whether the property_ref is not null (i.e., true if property_ref exists)
48
+ # this way, any non-null value is treated as true
49
+ # ex: "true", "1", "yes" => true; anything else => true; null/absent => false
50
+ to_boolean_template = Template(
51
+ "i.$node_property = coalesce(toBooleanOrNull($property_ref), ($property_ref IS NOT NULL))"
52
+ )
53
+ return to_boolean_template.safe_substitute(
54
+ node_property=f"_ont_{mapping_field.ontology_field}",
55
+ property_ref=property_ref,
56
+ )
57
+
58
+
59
+ def _build_ontology_field_statement_equal_boolean(
60
+ mapping_field: OntologyFieldMapping,
61
+ property_ref: PropertyRef,
62
+ ) -> str | None:
63
+ # we check if the property_ref is in the list of expected boolean values
64
+ equal_boolean_template = Template(
65
+ "i.$node_property = ($property_ref IN $property_values)"
66
+ )
67
+ extra_field_values = mapping_field.extra.get("values")
68
+ if extra_field_values is None:
69
+ # should not occure due to unit test but failing gracefully
70
+ logger.warning(
71
+ "equal_boolean special handling requires 'values' in extra for field %s",
72
+ mapping_field.ontology_field,
73
+ )
74
+ return None
75
+ if not isinstance(extra_field_values, list):
76
+ logger.warning(
77
+ "equal_boolean special handling 'values' in extra for field %s must be a list",
78
+ mapping_field.ontology_field,
79
+ )
80
+ return None
81
+ return equal_boolean_template.substitute(
82
+ node_property=f"_ont_{mapping_field.ontology_field}",
83
+ property_ref=property_ref,
84
+ property_values=extra_field_values,
85
+ )
86
+
87
+
88
+ def _escape_cypher_string(value: str) -> str:
89
+ r"""
90
+ Escape special characters in a string value for use in a Cypher string literal.
91
+
92
+ In Cypher, string literals are enclosed in double quotes, and the following characters
93
+ must be escaped with a backslash:
94
+ - Backslash (\) -> \\
95
+ - Double quote (") -> \"
96
+
97
+ :param value: The string value to escape
98
+ :return: The escaped string value safe for use in a Cypher string literal
99
+ """
100
+ # Escape backslashes first (must be done before escaping quotes)
101
+ escaped = value.replace("\\", "\\\\")
102
+ # Then escape double quotes
103
+ escaped = escaped.replace('"', '\\"')
104
+ return escaped
105
+
106
+
107
+ def _build_ontology_field_statement_static_value(
108
+ mapping_field: OntologyFieldMapping,
109
+ ) -> str | None:
110
+ # Sets a static value for the ontology field
111
+ # The value is provided in extra['value']
112
+ static_value_template = Template("i.$node_property = $static_value")
113
+ extra_value = mapping_field.extra.get("value")
114
+ if extra_value is None:
115
+ # should not occur due to unit test but failing gracefully
116
+ logger.warning(
117
+ "static_value special handling requires 'value' in extra for field %s",
118
+ mapping_field.ontology_field,
119
+ )
120
+ return None
121
+
122
+ # Format the value appropriately for Cypher
123
+ if isinstance(extra_value, str):
124
+ formatted_value = f'"{_escape_cypher_string(extra_value)}"'
125
+ elif isinstance(extra_value, bool):
126
+ formatted_value = str(extra_value).lower()
127
+ else:
128
+ formatted_value = str(extra_value)
129
+
130
+ return static_value_template.substitute(
131
+ node_property=f"_ont_{mapping_field.ontology_field}",
132
+ static_value=formatted_value,
133
+ )
134
+
135
+
136
+ def _build_ontology_field_statement_or_boolean(
137
+ mapping_field: OntologyFieldMapping,
138
+ node_property_map: dict[str, PropertyRef],
139
+ ) -> str | None:
140
+ # The or_clause is needed to avoid comparing nulls to boolean values
141
+ # See: https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/#cypher-null-logical-operators
142
+ or_clause = Template("coalesce(toBooleanOrNull($property_ref), false)")
143
+ or_boolean_template = Template("i.$node_property = ($property_condition)")
144
+ extra_fields = mapping_field.extra.get("fields")
145
+ if extra_fields is None:
146
+ # should not occure due to unit test but failing gracefully
147
+ logger.warning(
148
+ "or_boolean special handling requires 'fields' in extra for field %s",
149
+ mapping_field.ontology_field,
150
+ )
151
+ return None
152
+ if not isinstance(extra_fields, list):
153
+ # should not occure due to unit test but failing gracefully
154
+ logger.warning(
155
+ "or_boolean special handling 'fields' in extra for field %s must be a list",
156
+ mapping_field.ontology_field,
157
+ )
158
+ return None
159
+
160
+ property_conditions = [
161
+ or_clause.substitute(
162
+ property_ref=node_property_map.get(mapping_field.node_field),
163
+ )
164
+ ]
165
+ for extra_field in mapping_field.extra.get("fields", []):
166
+ extra_property_ref = node_property_map.get(extra_field)
167
+ if not extra_property_ref:
168
+ # should not occure due to unit test but failing gracefully
169
+ logger.warning(
170
+ "Extra field '%s' not found in node properties for or_boolean special handling of field %s",
171
+ extra_field,
172
+ mapping_field.ontology_field,
173
+ )
174
+ continue
175
+ property_conditions.append(
176
+ or_clause.substitute(
177
+ property_ref=extra_property_ref,
178
+ )
179
+ )
180
+ full_property_condition = " OR ".join(property_conditions)
181
+ return or_boolean_template.substitute(
182
+ node_property=f"_ont_{mapping_field.ontology_field}",
183
+ property_condition=full_property_condition,
184
+ )
185
+
186
+
187
+ def _build_ontology_field_statement_nor_boolean(
188
+ mapping_field: OntologyFieldMapping,
189
+ node_property_map: dict[str, PropertyRef],
190
+ ) -> str | None:
191
+ nor_clause = Template("NOT(coalesce(toBooleanOrNull($property_ref), false))")
192
+ nor_boolean_template = Template("i.$node_property = ($property_condition)")
193
+ extra_fields = mapping_field.extra.get("fields")
194
+ if extra_fields is None:
195
+ # should not occure due to unit test but failing gracefully
196
+ logger.warning(
197
+ "nor_boolean special handling requires 'fields' in extra for field %s",
198
+ mapping_field.ontology_field,
199
+ )
200
+ return None
201
+ if not isinstance(extra_fields, list):
202
+ # should not occure due to unit test but failing gracefully
203
+ logger.warning(
204
+ "nor_boolean special handling 'fields' in extra for field %s must be a list",
205
+ mapping_field.ontology_field,
206
+ )
207
+ return None
208
+
209
+ property_conditions = [
210
+ nor_clause.substitute(
211
+ property_ref=node_property_map.get(mapping_field.node_field),
212
+ )
213
+ ]
214
+ for extra_field in mapping_field.extra.get("fields", []):
215
+ extra_property_ref = node_property_map.get(extra_field)
216
+ if not extra_property_ref:
217
+ # should not occure due to unit test but failing gracefully
218
+ logger.warning(
219
+ "Extra field '%s' not found in node properties for nor_boolean special handling of field %s",
220
+ extra_field,
221
+ mapping_field.ontology_field,
222
+ )
223
+ continue
224
+ property_conditions.append(
225
+ nor_clause.substitute(
226
+ property_ref=extra_property_ref,
227
+ )
228
+ )
229
+ full_property_condition = " AND ".join(property_conditions)
230
+ return nor_boolean_template.substitute(
231
+ node_property=f"_ont_{mapping_field.ontology_field}",
232
+ property_condition=full_property_condition,
233
+ )
234
+
235
+
236
+ def _build_ontology_node_properties_statement(
237
+ node_schema: CartographyNodeSchema,
238
+ node_property_map: dict[str, PropertyRef],
239
+ ) -> str:
240
+ # DOC
241
+ # Try to get the mapping for the given node schema
242
+ ontology_mapping = get_semantic_label_mapping_from_node_schema(node_schema)
243
+ if not ontology_mapping:
244
+ return ""
245
+
246
+ source = _get_module_from_schema(node_schema).rsplit(":", maxsplit=1)[-1]
247
+ set_clauses = [f"i._ont_source = '{source}'"]
248
+ for mapping_field in ontology_mapping.fields:
249
+ ontology_field_name = f"_ont_{mapping_field.ontology_field}"
250
+ node_propertyref = node_property_map.get(mapping_field.node_field)
251
+
252
+ # Handle static_value special handling first - it doesn't require a node_field
253
+ if mapping_field.special_handling == "static_value":
254
+ static_value_statement = _build_ontology_field_statement_static_value(
255
+ mapping_field
256
+ )
257
+ if static_value_statement:
258
+ set_clauses.append(static_value_statement)
259
+ continue
260
+
261
+ # Skip validation for special_handling that don't require node_field
262
+ if not node_propertyref:
263
+ # This should not occure due to unit test but failing gracefully
264
+ logger.warning(
265
+ "Field '%s' not found in node properties for node schema %s",
266
+ mapping_field.node_field,
267
+ node_schema.__class__.__name__,
268
+ )
269
+ continue
270
+ if mapping_field.special_handling == "invert_boolean":
271
+ set_clauses.append(
272
+ _build_ontology_field_statement_invert_boolean(
273
+ mapping_field,
274
+ node_propertyref,
275
+ )
276
+ )
277
+ elif mapping_field.special_handling == "to_boolean":
278
+ set_clauses.append(
279
+ _build_ontology_field_statement_to_boolean(
280
+ mapping_field,
281
+ node_propertyref,
282
+ )
283
+ )
284
+ elif mapping_field.special_handling == "equal_boolean":
285
+ equal_boolean_statement = _build_ontology_field_statement_equal_boolean(
286
+ mapping_field,
287
+ node_propertyref,
288
+ )
289
+ if equal_boolean_statement:
290
+ set_clauses.append(equal_boolean_statement)
291
+ elif mapping_field.special_handling == "or_boolean":
292
+ or_boolean_statement = _build_ontology_field_statement_or_boolean(
293
+ mapping_field, node_property_map
294
+ )
295
+ if or_boolean_statement:
296
+ set_clauses.append(or_boolean_statement)
297
+ elif mapping_field.special_handling == "nor_boolean":
298
+ nor_boolean_statement = _build_ontology_field_statement_nor_boolean(
299
+ mapping_field, node_property_map
300
+ )
301
+ if nor_boolean_statement:
302
+ set_clauses.append(nor_boolean_statement)
303
+ else:
304
+ simple_field_template = Template("i.$node_property = $property_ref")
305
+ set_clauses.append(
306
+ simple_field_template.substitute(
307
+ node_property=ontology_field_name,
308
+ property_ref=node_propertyref,
309
+ )
310
+ )
311
+ if len(set_clauses) == 0:
312
+ return ""
313
+ # Add initial newline
314
+ return ",\n" + ",\n".join(set_clauses)
315
+
316
+
22
317
  def _build_node_properties_statement(
23
- node_property_map: Dict[str, PropertyRef],
24
- extra_node_labels: Optional[ExtraNodeLabels] = None,
318
+ node_property_map: dict[str, PropertyRef],
319
+ extra_node_labels: ExtraNodeLabels | None = None,
25
320
  ) -> str:
26
321
  """
27
322
  Generate a Neo4j clause that sets node properties using the given mapping of attribute names to PropertyRefs.
28
323
 
29
324
  As seen in this example,
30
325
 
31
- node_property_map: Dict[str, PropertyRef] = {
326
+ node_property_map: dict[str, PropertyRef] = {
32
327
  'id': PropertyRef("Id"),
33
328
  'node_prop_1': PropertyRef("Prop1"),
34
329
  'node_prop_2': PropertyRef("Prop2", set_in_kwargs=True),
@@ -46,29 +341,38 @@ def _build_node_properties_statement(
46
341
  :param extra_node_labels: Optional ExtraNodeLabels object to set on the node as string
47
342
  :return: The resulting Neo4j SET clause to set the given attributes on the node
48
343
  """
49
- ingest_fields_template = Template('i.$node_property = $property_ref')
344
+ ingest_fields_template = Template("i.$node_property = $property_ref")
50
345
 
51
- set_clause = ',\n'.join([
52
- ingest_fields_template.safe_substitute(node_property=node_property, property_ref=property_ref)
53
- for node_property, property_ref in node_property_map.items()
54
- if node_property != 'id' # The `MERGE` clause will have already set `id`; let's not set it again.
55
- ])
346
+ set_clause = ",\n".join(
347
+ [
348
+ ingest_fields_template.safe_substitute(
349
+ node_property=node_property,
350
+ property_ref=property_ref,
351
+ )
352
+ for node_property, property_ref in node_property_map.items()
353
+ if node_property
354
+ != "id" # The `MERGE` clause will have already set `id`; let's not set it again.
355
+ ],
356
+ )
56
357
 
57
358
  # Set extra labels on the node if specified
58
359
  if extra_node_labels:
59
- extra_labels = ':'.join([label for label in extra_node_labels.labels])
360
+ extra_labels = ":".join([label for label in extra_node_labels.labels])
60
361
  set_clause += f",\n i:{extra_labels}"
61
362
  return set_clause
62
363
 
63
364
 
64
- def _build_rel_properties_statement(rel_var: str, rel_property_map: Optional[Dict[str, PropertyRef]] = None) -> str:
365
+ def _build_rel_properties_statement(
366
+ rel_var: str,
367
+ rel_property_map: dict[str, PropertyRef] | None = None,
368
+ ) -> str:
65
369
  """
66
370
  Generate a Neo4j clause that sets relationship properties using the given mapping of attribute names to
67
371
  PropertyRefs.
68
372
 
69
373
  In this code example:
70
374
 
71
- rel_property_map: Dict[str, PropertyRef] = {
375
+ rel_property_map: dict[str, PropertyRef] = {
72
376
  'rel_prop_1': PropertyRef("Prop1"),
73
377
  'rel_prop_2': PropertyRef("Prop2", static=True),
74
378
  }
@@ -83,33 +387,41 @@ def _build_rel_properties_statement(rel_var: str, rel_property_map: Optional[Dic
83
387
  :param rel_property_map: Mapping of relationship attribute names as str to PropertyRef objects
84
388
  :return: The resulting Neo4j SET clause to set the given attributes on the relationship
85
389
  """
86
- set_clause = ''
87
- ingest_fields_template = Template('$rel_var.$rel_property = $property_ref')
390
+ set_clause = ""
391
+ ingest_fields_template = Template("$rel_var.$rel_property = $property_ref")
88
392
 
89
393
  if rel_property_map:
90
- set_clause += ',\n'.join([
91
- ingest_fields_template.safe_substitute(
92
- rel_var=rel_var,
93
- rel_property=rel_property,
94
- property_ref=property_ref,
95
- )
96
- for rel_property, property_ref in rel_property_map.items()
97
- ])
394
+ set_clause += ",\n".join(
395
+ [
396
+ ingest_fields_template.safe_substitute(
397
+ rel_var=rel_var,
398
+ rel_property=rel_property,
399
+ property_ref=property_ref,
400
+ )
401
+ for rel_property, property_ref in rel_property_map.items()
402
+ ],
403
+ )
98
404
  return set_clause
99
405
 
100
406
 
101
- def _build_match_clause(matcher: TargetNodeMatcher) -> str:
407
+ def _build_match_clause(matcher: TargetNodeMatcher | SourceNodeMatcher) -> str:
102
408
  """
103
409
  Generate a Neo4j match statement on one or more keys and values for a given node.
104
- :param matcher: A TargetNodeMatcher object
410
+ :param matcher: A TargetNodeMatcher or SourceNodeMatcher object
105
411
  :return: a Neo4j match clause
106
412
  """
107
413
  match = Template("$Key: $PropRef")
108
414
  matcher_asdict = asdict(matcher)
109
- return ', '.join(match.safe_substitute(Key=key, PropRef=prop_ref) for key, prop_ref in matcher_asdict.items())
415
+ return ", ".join(
416
+ match.safe_substitute(Key=key, PropRef=prop_ref)
417
+ for key, prop_ref in matcher_asdict.items()
418
+ )
110
419
 
111
420
 
112
- def _build_where_clause_for_rel_match(node_var: str, matcher: TargetNodeMatcher) -> str:
421
+ def _build_where_clause_for_rel_match(
422
+ node_var: str,
423
+ matcher: TargetNodeMatcher,
424
+ ) -> str:
113
425
  """
114
426
  Same as _build_match_clause, but puts the matching logic in a WHERE clause.
115
427
  This is intended specifically to use for joining with relationships where we need a case-insensitive match.
@@ -118,39 +430,70 @@ def _build_where_clause_for_rel_match(node_var: str, matcher: TargetNodeMatcher)
118
430
  """
119
431
  match = Template("$node_var.$key = $prop_ref")
120
432
  case_insensitive_match = Template("toLower($node_var.$key) = toLower($prop_ref)")
433
+ fuzzy_and_ignorecase_match = Template(
434
+ "toLower($node_var.$key) CONTAINS toLower($prop_ref)"
435
+ )
436
+ # This assumes that item.$prop_ref points to a list available on the data object
437
+ one_to_many_match = Template("$node_var.$key IN $prop_ref")
121
438
 
122
439
  matcher_asdict = asdict(matcher)
123
440
 
124
441
  result = []
125
442
  for key, prop_ref in matcher_asdict.items():
126
443
  if prop_ref.ignore_case:
127
- prop_line = case_insensitive_match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
444
+ prop_line = case_insensitive_match.safe_substitute(
445
+ node_var=node_var,
446
+ key=key,
447
+ prop_ref=prop_ref,
448
+ )
449
+ elif prop_ref.fuzzy_and_ignore_case:
450
+ prop_line = fuzzy_and_ignorecase_match.safe_substitute(
451
+ node_var=node_var, key=key, prop_ref=prop_ref
452
+ )
453
+ elif prop_ref.one_to_many:
454
+ # Allow a single node to be attached to multiple others at once using a list of IDs provided in kwargs
455
+ prop_line = one_to_many_match.safe_substitute(
456
+ node_var=node_var, key=key, prop_ref=prop_ref
457
+ )
128
458
  else:
129
- prop_line = match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
459
+ # Exact match (default; most efficient)
460
+ prop_line = match.safe_substitute(
461
+ node_var=node_var,
462
+ key=key,
463
+ prop_ref=prop_ref,
464
+ )
130
465
  result.append(prop_line)
131
- return ' AND\n'.join(result)
466
+ return " AND\n".join(result)
132
467
 
133
468
 
134
- def _asdict_with_validate_relprops(link: CartographyRelSchema) -> Dict[str, PropertyRef]:
469
+ def _asdict_with_validate_relprops(
470
+ link: CartographyRelSchema,
471
+ ) -> dict[str, PropertyRef]:
135
472
  """
136
473
  Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
137
474
  isn't always caught by IDEs.
138
475
  """
139
476
  try:
140
- rel_props_as_dict: Dict[str, PropertyRef] = asdict(link.properties)
477
+ rel_props_as_dict: dict[str, PropertyRef] = asdict(link.properties)
141
478
  except TypeError as e:
142
- if e.args and e.args[0] and e.args == 'asdict() should be called on dataclass instances':
479
+ if (
480
+ e.args
481
+ and e.args[0]
482
+ and e.args == "asdict() should be called on dataclass instances"
483
+ ):
143
484
  logger.error(
144
485
  f'TypeError thrown when trying to draw relation "{link.rel_label}" to a "{link.target_node_label}" '
145
- f'node. Please make sure that you did not forget to write `()` when specifying `properties` in the'
146
- f'dataclass. '
147
- f'For example, do `properties: RelProp = RelProp()`; NOT `properties: RelProp = RelProp`.',
486
+ f"node. Please make sure that you did not forget to write `()` when specifying `properties` in the"
487
+ f"dataclass. "
488
+ f"For example, do `properties: RelProp = RelProp()`; NOT `properties: RelProp = RelProp`.",
148
489
  )
149
490
  raise
150
491
  return rel_props_as_dict
151
492
 
152
493
 
153
- def _build_attach_sub_resource_statement(sub_resource_link: Optional[CartographyRelSchema] = None) -> str:
494
+ def _build_attach_sub_resource_statement(
495
+ sub_resource_link: CartographyRelSchema | None = None,
496
+ ) -> str:
154
497
  """
155
498
  Generates a Neo4j statement to attach a sub resource to a node. A 'sub resource' is a term we made up to describe
156
499
  billing units of a given resource. For example,
@@ -164,7 +507,7 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
164
507
  keys, and directionality. If sub_resource_link is None, return an empty string.
165
508
  """
166
509
  if not sub_resource_link:
167
- return ''
510
+ return ""
168
511
 
169
512
  sub_resource_attach_template = Template(
170
513
  """
@@ -174,6 +517,8 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
174
517
  $RelMergeClause
175
518
  ON CREATE SET r.firstseen = timestamp()
176
519
  SET
520
+ r._module_name = "$module_name",
521
+ r._module_version = "$module_version",
177
522
  $set_rel_properties_statement
178
523
  """,
179
524
  )
@@ -183,22 +528,31 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
183
528
  else:
184
529
  rel_merge_template = Template("""MERGE (i)-[r:$SubResourceRelLabel]->(j)""")
185
530
 
186
- rel_merge_clause = rel_merge_template.safe_substitute(SubResourceRelLabel=sub_resource_link.rel_label)
531
+ rel_merge_clause = rel_merge_template.safe_substitute(
532
+ SubResourceRelLabel=sub_resource_link.rel_label,
533
+ )
187
534
 
188
- rel_props_as_dict: Dict[str, PropertyRef] = _asdict_with_validate_relprops(sub_resource_link)
535
+ rel_props_as_dict: dict[str, PropertyRef] = _asdict_with_validate_relprops(
536
+ sub_resource_link,
537
+ )
189
538
 
190
539
  attach_sub_resource_statement = sub_resource_attach_template.safe_substitute(
191
540
  SubResourceLabel=sub_resource_link.target_node_label,
192
541
  MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
193
542
  RelMergeClause=rel_merge_clause,
543
+ module_name=_get_module_from_schema(sub_resource_link),
544
+ module_version=_get_cartography_version(),
194
545
  SubResourceRelLabel=sub_resource_link.rel_label,
195
- set_rel_properties_statement=_build_rel_properties_statement('r', rel_props_as_dict),
546
+ set_rel_properties_statement=_build_rel_properties_statement(
547
+ "r",
548
+ rel_props_as_dict,
549
+ ),
196
550
  )
197
551
  return attach_sub_resource_statement
198
552
 
199
553
 
200
554
  def _build_attach_additional_links_statement(
201
- additional_relationships: Optional[OtherRelationships] = None,
555
+ additional_relationships: OtherRelationships | None = None,
202
556
  ) -> str:
203
557
  """
204
558
  Generates a Neo4j statement to attach one or more CartographyRelSchemas to node(s) previously mentioned in the
@@ -210,7 +564,7 @@ def _build_attach_additional_links_statement(
210
564
  labels, attribute keys, and directionality. If additional_relationships is None, return an empty string.
211
565
  """
212
566
  if not additional_relationships:
213
- return ''
567
+ return ""
214
568
 
215
569
  additional_links_template = Template(
216
570
  """
@@ -222,6 +576,8 @@ def _build_attach_additional_links_statement(
222
576
  $RelMerge
223
577
  ON CREATE SET $rel_var.firstseen = timestamp()
224
578
  SET
579
+ $rel_var._module_name = "$module_name",
580
+ $rel_var._module_version = "$module_version",
225
581
  $set_rel_properties_statement
226
582
  """,
227
583
  )
@@ -231,9 +587,13 @@ def _build_attach_additional_links_statement(
231
587
  rel_var = f"r{num}"
232
588
 
233
589
  if link.direction == LinkDirection.INWARD:
234
- rel_merge_template = Template("""MERGE (i)<-[$rel_var:$AddlRelLabel]-($node_var)""")
590
+ rel_merge_template = Template(
591
+ """MERGE (i)<-[$rel_var:$AddlRelLabel]-($node_var)""",
592
+ )
235
593
  else:
236
- rel_merge_template = Template("""MERGE (i)-[$rel_var:$AddlRelLabel]->($node_var)""")
594
+ rel_merge_template = Template(
595
+ """MERGE (i)-[$rel_var:$AddlRelLabel]->($node_var)""",
596
+ )
237
597
 
238
598
  rel_merge = rel_merge_template.safe_substitute(
239
599
  rel_var=rel_var,
@@ -245,20 +605,28 @@ def _build_attach_additional_links_statement(
245
605
 
246
606
  additional_ref = additional_links_template.safe_substitute(
247
607
  AddlLabel=link.target_node_label,
248
- WhereClause=_build_where_clause_for_rel_match(node_var, link.target_node_matcher),
608
+ WhereClause=_build_where_clause_for_rel_match(
609
+ node_var,
610
+ link.target_node_matcher,
611
+ ),
249
612
  node_var=node_var,
250
613
  rel_var=rel_var,
251
614
  RelMerge=rel_merge,
252
- set_rel_properties_statement=_build_rel_properties_statement(rel_var, rel_props_as_dict),
615
+ module_name=_get_module_from_schema(link),
616
+ module_version=_get_cartography_version(),
617
+ set_rel_properties_statement=_build_rel_properties_statement(
618
+ rel_var,
619
+ rel_props_as_dict,
620
+ ),
253
621
  )
254
622
  links.append(additional_ref)
255
623
 
256
- return 'UNION'.join(links)
624
+ return "UNION".join(links)
257
625
 
258
626
 
259
627
  def _build_attach_relationships_statement(
260
- sub_resource_relationship: Optional[CartographyRelSchema],
261
- other_relationships: Optional[OtherRelationships],
628
+ sub_resource_relationship: CartographyRelSchema | None,
629
+ other_relationships: OtherRelationships | None,
262
630
  ) -> str:
263
631
  """
264
632
  Use Neo4j subqueries to attach sub resource and/or other relationships.
@@ -271,14 +639,22 @@ def _build_attach_relationships_statement(
271
639
  if not sub_resource_relationship and not other_relationships:
272
640
  return ""
273
641
 
274
- attach_sub_resource_statement = _build_attach_sub_resource_statement(sub_resource_relationship)
275
- attach_additional_links_statement = _build_attach_additional_links_statement(other_relationships)
642
+ attach_sub_resource_statement = _build_attach_sub_resource_statement(
643
+ sub_resource_relationship,
644
+ )
645
+ attach_additional_links_statement = _build_attach_additional_links_statement(
646
+ other_relationships,
647
+ )
276
648
 
277
649
  statements = []
278
- statements += [attach_sub_resource_statement] if attach_sub_resource_statement else []
279
- statements += [attach_additional_links_statement] if attach_additional_links_statement else []
650
+ statements += (
651
+ [attach_sub_resource_statement] if attach_sub_resource_statement else []
652
+ )
653
+ statements += (
654
+ [attach_additional_links_statement] if attach_additional_links_statement else []
655
+ )
280
656
 
281
- attach_relationships_statement = 'UNION'.join(stmt for stmt in statements)
657
+ attach_relationships_statement = "UNION".join(stmt for stmt in statements)
282
658
 
283
659
  query_template = Template(
284
660
  """
@@ -288,12 +664,14 @@ def _build_attach_relationships_statement(
288
664
  }
289
665
  """,
290
666
  )
291
- return query_template.safe_substitute(attach_relationships_statement=attach_relationships_statement)
667
+ return query_template.safe_substitute(
668
+ attach_relationships_statement=attach_relationships_statement,
669
+ )
292
670
 
293
671
 
294
672
  def rel_present_on_node_schema(
295
- node_schema: CartographyNodeSchema,
296
- rel_schema: CartographyRelSchema,
673
+ node_schema: CartographyNodeSchema,
674
+ rel_schema: CartographyRelSchema,
297
675
  ) -> bool:
298
676
  """
299
677
  Answers the question: is the given rel_schema is present on the given node_schema?
@@ -305,9 +683,9 @@ def rel_present_on_node_schema(
305
683
 
306
684
 
307
685
  def filter_selected_relationships(
308
- node_schema: CartographyNodeSchema,
309
- selected_relationships: Set[CartographyRelSchema],
310
- ) -> Tuple[Optional[CartographyRelSchema], Optional[OtherRelationships]]:
686
+ node_schema: CartographyNodeSchema,
687
+ selected_relationships: set[CartographyRelSchema],
688
+ ) -> tuple[CartographyRelSchema | None, OtherRelationships | None]:
311
689
  """
312
690
  Ensures that selected relationships specified to build_ingestion_query() are actually present on
313
691
  node_schema.sub_resource_relationship and node_schema.other_relationships.
@@ -341,14 +719,16 @@ def filter_selected_relationships(
341
719
  sub_resource_rel = None
342
720
 
343
721
  # By this point, everything in selected_relationships is validated to be present in node_schema
344
- filtered_other_rels = OtherRelationships([rel for rel in selected_relationships if rel != sub_resource_rel])
722
+ filtered_other_rels = OtherRelationships(
723
+ [rel for rel in selected_relationships if rel != sub_resource_rel],
724
+ )
345
725
 
346
726
  return sub_resource_rel, filtered_other_rels
347
727
 
348
728
 
349
729
  def build_ingestion_query(
350
- node_schema: CartographyNodeSchema,
351
- selected_relationships: Optional[Set[CartographyRelSchema]] = None,
730
+ node_schema: CartographyNodeSchema,
731
+ selected_relationships: set[CartographyRelSchema] | None = None,
352
732
  ) -> str:
353
733
  """
354
734
  Generates a Neo4j query from the given CartographyNodeSchema to ingest the specified nodes and relationships so that
@@ -375,59 +755,81 @@ def build_ingestion_query(
375
755
  MERGE (i:$node_label{id: $dict_id_field})
376
756
  ON CREATE SET i.firstseen = timestamp()
377
757
  SET
758
+ i._module_name = "$module_name",
759
+ i._module_version = "$module_version",
378
760
  $set_node_properties_statement
761
+ $set_ontology_node_properties_statement
379
762
  $attach_relationships_statement
380
763
  """,
381
764
  )
382
765
 
383
766
  node_props: CartographyNodeProperties = node_schema.properties
384
- node_props_as_dict: Dict[str, PropertyRef] = asdict(node_props)
767
+ node_props_as_dict: dict[str, PropertyRef] = asdict(node_props)
385
768
 
386
769
  # Handle selected relationships
387
- sub_resource_rel: Optional[CartographyRelSchema] = node_schema.sub_resource_relationship
388
- other_rels: Optional[OtherRelationships] = node_schema.other_relationships
770
+ sub_resource_rel: CartographyRelSchema | None = (
771
+ node_schema.sub_resource_relationship
772
+ )
773
+ other_rels: OtherRelationships | None = node_schema.other_relationships
389
774
  if selected_relationships or selected_relationships == set():
390
- sub_resource_rel, other_rels = filter_selected_relationships(node_schema, selected_relationships)
775
+ sub_resource_rel, other_rels = filter_selected_relationships(
776
+ node_schema,
777
+ selected_relationships,
778
+ )
391
779
 
392
780
  ingest_query = query_template.safe_substitute(
393
781
  node_label=node_schema.label,
394
782
  dict_id_field=node_props.id,
783
+ module_name=_get_module_from_schema(node_schema),
784
+ module_version=_get_cartography_version(),
395
785
  set_node_properties_statement=_build_node_properties_statement(
396
786
  node_props_as_dict,
397
787
  node_schema.extra_node_labels,
398
788
  ),
399
- attach_relationships_statement=_build_attach_relationships_statement(sub_resource_rel, other_rels),
789
+ set_ontology_node_properties_statement=_build_ontology_node_properties_statement(
790
+ node_schema,
791
+ node_props_as_dict,
792
+ ),
793
+ attach_relationships_statement=_build_attach_relationships_statement(
794
+ sub_resource_rel,
795
+ other_rels,
796
+ ),
400
797
  )
401
798
  return ingest_query
402
799
 
403
800
 
404
- def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
801
+ def build_create_index_queries(node_schema: CartographyNodeSchema) -> list[str]:
405
802
  """
406
803
  Generate queries to create indexes for the given CartographyNodeSchema and all node types attached to it via its
407
804
  relationships.
408
805
  :param node_schema: The Cartography node_schema object
409
806
  :return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
410
807
  """
411
- index_template = Template('CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);')
808
+ index_template = Template(
809
+ "CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);",
810
+ )
412
811
 
413
812
  # First ensure an index exists for the node_schema and all extra labels on the `id` and `lastupdated` fields
414
813
  result = [
415
814
  index_template.safe_substitute(
416
815
  TargetNodeLabel=node_schema.label,
417
- TargetAttribute='id',
816
+ TargetAttribute="id",
418
817
  ),
419
818
  index_template.safe_substitute(
420
819
  TargetNodeLabel=node_schema.label,
421
- TargetAttribute='lastupdated',
820
+ TargetAttribute="lastupdated",
422
821
  ),
423
822
  ]
424
823
  if node_schema.extra_node_labels:
425
- result.extend([
426
- index_template.safe_substitute(
427
- TargetNodeLabel=label,
428
- TargetAttribute='id', # Precondition: 'id' is defined on all cartography node_schema objects.
429
- ) for label in node_schema.extra_node_labels.labels
430
- ])
824
+ result.extend(
825
+ [
826
+ index_template.safe_substitute(
827
+ TargetNodeLabel=label,
828
+ TargetAttribute="id", # Precondition: 'id' is defined on all cartography node_schema objects.
829
+ )
830
+ for label in node_schema.extra_node_labels.labels
831
+ ],
832
+ )
431
833
 
432
834
  # Next, for all relationships possible out of this node, ensure that indexes exist for all target nodes' properties
433
835
  # as specified in their TargetNodeMatchers.
@@ -439,15 +841,211 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
439
841
  for rs in rel_schemas:
440
842
  for target_key in asdict(rs.target_node_matcher).keys():
441
843
  result.append(
442
- index_template.safe_substitute(TargetNodeLabel=rs.target_node_label, TargetAttribute=target_key),
844
+ index_template.safe_substitute(
845
+ TargetNodeLabel=rs.target_node_label,
846
+ TargetAttribute=target_key,
847
+ ),
443
848
  )
444
849
 
445
850
  # Now, include extra indexes defined by the module author on the node schema's property refs.
446
- node_props_as_dict: Dict[str, PropertyRef] = asdict(node_schema.properties)
447
- result.extend([
448
- index_template.safe_substitute(
449
- TargetNodeLabel=node_schema.label,
450
- TargetAttribute=prop_name,
451
- ) for prop_name, prop_ref in node_props_as_dict.items() if prop_ref.extra_index
452
- ])
851
+ node_props_as_dict: dict[str, PropertyRef] = asdict(node_schema.properties)
852
+ result.extend(
853
+ [
854
+ index_template.safe_substitute(
855
+ TargetNodeLabel=node_schema.label,
856
+ TargetAttribute=prop_name,
857
+ )
858
+ for prop_name, prop_ref in node_props_as_dict.items()
859
+ if prop_ref.extra_index
860
+ ],
861
+ )
453
862
  return result
863
+
864
+
865
+ def build_create_index_queries_for_matchlink(
866
+ rel_schema: CartographyRelSchema,
867
+ ) -> list[str]:
868
+ """
869
+ Generate queries to create indexes for the given CartographyRelSchema and all node types attached to it via its
870
+ relationships.
871
+ :param rel_schema: The CartographyRelSchema object
872
+ :return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
873
+ """
874
+ if not rel_schema.source_node_matcher:
875
+ logger.warning(
876
+ f"No source node matcher found for {rel_schema.rel_label}; returning empty list."
877
+ "Please note that build_create_index_queries_for_matchlink() is only used for load_matchlinks() where we match on "
878
+ "and connect existing nodes in the graph."
879
+ )
880
+ return []
881
+
882
+ index_template = Template(
883
+ "CREATE INDEX IF NOT EXISTS FOR (n:$NodeLabel) ON (n.$NodeAttribute);",
884
+ )
885
+
886
+ result = []
887
+ for source_key in asdict(rel_schema.source_node_matcher).keys():
888
+ result.append(
889
+ index_template.safe_substitute(
890
+ NodeLabel=rel_schema.source_node_label,
891
+ NodeAttribute=source_key,
892
+ ),
893
+ )
894
+ for target_key in asdict(rel_schema.target_node_matcher).keys():
895
+ result.append(
896
+ index_template.safe_substitute(
897
+ NodeLabel=rel_schema.target_node_label,
898
+ NodeAttribute=target_key,
899
+ ),
900
+ )
901
+
902
+ # Create a composite index for the relationship between the source and target nodes.
903
+ # https://neo4j.com/docs/cypher-manual/4.3/indexes-for-search-performance/#administration-indexes-create-a-composite-index-for-relationships
904
+ rel_index_template = Template(
905
+ "CREATE INDEX IF NOT EXISTS FOR ()$rel_direction[r:$RelLabel]$rel_direction_end() "
906
+ "ON (r.lastupdated, r._sub_resource_label, r._sub_resource_id);",
907
+ )
908
+ if rel_schema.direction == LinkDirection.INWARD:
909
+ result.append(
910
+ rel_index_template.safe_substitute(
911
+ RelLabel=rel_schema.rel_label,
912
+ rel_direction="<-",
913
+ rel_direction_end="-",
914
+ )
915
+ )
916
+ else:
917
+ result.append(
918
+ rel_index_template.safe_substitute(
919
+ RelLabel=rel_schema.rel_label,
920
+ rel_direction="-",
921
+ rel_direction_end="->",
922
+ )
923
+ )
924
+ return result
925
+
926
+
927
+ def build_matchlink_query(rel_schema: CartographyRelSchema) -> str:
928
+ """
929
+ Generate a Neo4j query to link two existing nodes when given a CartographyRelSchema object.
930
+ This is only used for load_matchlinks().
931
+ :param rel_schema: The CartographyRelSchema object to generate a query. This CartographyRelSchema object
932
+ - Must have a source_node_matcher and source_node_label defined
933
+ - Must have a CartographyRelProperties object where _sub_resource_label and _sub_resource_id are defined
934
+ :return: A Neo4j query that can be used to link two existing nodes.
935
+ """
936
+ if not rel_schema.source_node_matcher or not rel_schema.source_node_label:
937
+ raise ValueError(
938
+ f"No source node matcher or source node label found for {rel_schema.rel_label}. "
939
+ "MatchLink relationships require a source_node_matcher and source_node_label to be defined."
940
+ )
941
+
942
+ rel_props_as_dict = _asdict_with_validate_relprops(rel_schema)
943
+
944
+ # These are needed for the cleanup query
945
+ if "_sub_resource_label" not in rel_props_as_dict:
946
+ raise ValueError(
947
+ f"Expected _sub_resource_label to be defined on {rel_schema.properties.__class__.__name__}"
948
+ "Please include `_sub_resource_label: PropertyRef = PropertyRef('_sub_resource_label', set_in_kwargs=True)`"
949
+ )
950
+ if "_sub_resource_id" not in rel_props_as_dict:
951
+ raise ValueError(
952
+ f"Expected _sub_resource_id to be defined on {rel_schema.properties.__class__.__name__}"
953
+ "Please include `_sub_resource_id: PropertyRef = PropertyRef('_sub_resource_id', set_in_kwargs=True)`"
954
+ )
955
+
956
+ matchlink_query_template = Template(
957
+ """
958
+ UNWIND $DictList as item
959
+ $source_match
960
+ $target_match
961
+ MERGE $rel
962
+ ON CREATE SET r.firstseen = timestamp()
963
+ SET
964
+ r._module_name = "$module_name",
965
+ r._module_version = "$module_version",
966
+ $set_rel_properties_statement;
967
+ """
968
+ )
969
+
970
+ source_match = Template(
971
+ "MATCH (from:$source_node_label{$match_clause})"
972
+ ).safe_substitute(
973
+ source_node_label=rel_schema.source_node_label,
974
+ match_clause=_build_match_clause(rel_schema.source_node_matcher),
975
+ )
976
+
977
+ target_match = Template(
978
+ "MATCH (to:$target_node_label{$match_clause})"
979
+ ).safe_substitute(
980
+ target_node_label=rel_schema.target_node_label,
981
+ match_clause=_build_match_clause(rel_schema.target_node_matcher),
982
+ )
983
+
984
+ if rel_schema.direction == LinkDirection.INWARD:
985
+ rel = f"(from)<-[r:{rel_schema.rel_label}]-(to)"
986
+ else:
987
+ rel = f"(from)-[r:{rel_schema.rel_label}]->(to)"
988
+
989
+ return matchlink_query_template.safe_substitute(
990
+ source_match=source_match,
991
+ target_match=target_match,
992
+ rel=rel,
993
+ module_name=_get_module_from_schema(rel_schema),
994
+ module_version=_get_cartography_version(),
995
+ set_rel_properties_statement=_build_rel_properties_statement(
996
+ "r",
997
+ rel_props_as_dict,
998
+ ),
999
+ )
1000
+
1001
+
1002
+ def _get_cartography_version() -> str:
1003
+ """
1004
+ Get the current version of the cartography package.
1005
+
1006
+ This function attempts to retrieve the version of the installed cartography package
1007
+ using importlib.metadata. If the package is not found (typically in development
1008
+ or testing environments), it returns 'dev' as a fallback.
1009
+
1010
+ Returns:
1011
+ The version string of the cartography package, or 'dev' if not found
1012
+ """
1013
+ try:
1014
+ return version("cartography")
1015
+ except PackageNotFoundError:
1016
+ # This can occured if the cartography package is not installed in the environment, typically in development or testing environments.
1017
+ logger.warning("cartography package not found. Returning 'dev' version.")
1018
+ # Fallback to reading the VERSION file if the package is not found
1019
+ return "dev"
1020
+
1021
+
1022
+ def _get_module_from_schema(
1023
+ schema, #: "CartographyNodeSchema" | "CartographyRelSchema",
1024
+ ) -> str:
1025
+ """
1026
+ Extract the module name from a Cartography schema object.
1027
+
1028
+ This function extracts and formats the module name from a CartographyNodeSchema
1029
+ or CartographyRelSchema object. It expects schemas to be part of the official
1030
+ cartography.models package hierarchy and returns a formatted string indicating
1031
+ the specific cartography module.
1032
+
1033
+ Args:
1034
+ schema: A CartographyNodeSchema or CartographyRelSchema object
1035
+
1036
+ Returns:
1037
+ A formatted module name string in the format 'cartography:<module_name>'
1038
+ or 'unknown:<full_module_path>' if the schema is not from cartography.models
1039
+ """
1040
+ # If the entity schema does not belong to the cartography.models package,
1041
+ # we log a warning and return the full module path.
1042
+ if not schema.__module__.startswith("cartography.models."):
1043
+ logger.warning(
1044
+ "The schema %s does not start with 'cartography.models.'. "
1045
+ "This may indicate that the schema is not part of the official cartography models.",
1046
+ schema.__module__,
1047
+ )
1048
+ return f"unknown:{schema.__module__}"
1049
+ # Otherwise, we return the module path as a string.
1050
+ parts = schema.__module__.split(".")
1051
+ return f"cartography:{parts[2]}"