cartography 0.104.0rc2__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.
- cartography/_version.py +16 -3
- cartography/cli.py +466 -5
- cartography/client/aws/__init__.py +19 -0
- cartography/client/aws/ecr.py +51 -0
- cartography/client/core/tx.py +357 -8
- cartography/config.py +153 -0
- cartography/data/azure_permission_relationships.yaml +20 -0
- cartography/data/gcp_permission_relationships.yaml +21 -0
- cartography/data/indexes.cypher +0 -186
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
- cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/data/jobs/cleanup/github_repos_cleanup.json +2 -0
- cartography/driftdetect/cli.py +3 -2
- cartography/graph/cleanupbuilder.py +198 -41
- cartography/graph/job.py +54 -6
- cartography/graph/querybuilder.py +528 -27
- cartography/graph/statement.py +5 -1
- cartography/intel/airbyte/__init__.py +105 -0
- cartography/intel/airbyte/connections.py +120 -0
- cartography/intel/airbyte/destinations.py +81 -0
- cartography/intel/airbyte/organizations.py +59 -0
- cartography/intel/airbyte/sources.py +78 -0
- cartography/intel/airbyte/tags.py +64 -0
- cartography/intel/airbyte/users.py +106 -0
- cartography/intel/airbyte/util.py +122 -0
- cartography/intel/airbyte/workspaces.py +63 -0
- cartography/intel/aws/__init__.py +24 -9
- cartography/intel/aws/acm.py +124 -0
- cartography/intel/aws/apigateway.py +253 -22
- cartography/intel/aws/apigatewayv2.py +116 -0
- cartography/intel/aws/cloudtrail.py +17 -39
- cartography/intel/aws/cloudtrail_management_events.py +962 -0
- cartography/intel/aws/cloudwatch.py +150 -4
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/cognito.py +201 -0
- cartography/intel/aws/config.py +7 -3
- cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
- cartography/intel/aws/ec2/instances.py +25 -1
- cartography/intel/aws/ec2/internet_gateways.py +4 -2
- cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
- cartography/intel/aws/ec2/network_interfaces.py +5 -1
- cartography/intel/aws/ec2/reserved_instances.py +3 -1
- cartography/intel/aws/ec2/security_groups.py +140 -122
- cartography/intel/aws/ec2/snapshots.py +47 -84
- cartography/intel/aws/ec2/subnets.py +37 -63
- cartography/intel/aws/ec2/tgw.py +11 -5
- cartography/intel/aws/ec2/volumes.py +1 -1
- cartography/intel/aws/ec2/vpc.py +140 -124
- cartography/intel/aws/ec2/vpc_peerings.py +262 -125
- cartography/intel/aws/ecr.py +269 -98
- cartography/intel/aws/ecr_image_layers.py +923 -0
- cartography/intel/aws/ecs.py +251 -380
- cartography/intel/aws/efs.py +179 -11
- cartography/intel/aws/elasticache.py +102 -79
- cartography/intel/aws/elasticsearch.py +13 -4
- cartography/intel/aws/eventbridge.py +164 -0
- cartography/intel/aws/glue.py +181 -0
- cartography/intel/aws/guardduty.py +443 -0
- cartography/intel/aws/iam.py +750 -493
- cartography/intel/aws/identitycenter.py +605 -83
- cartography/intel/aws/inspector.py +221 -105
- cartography/intel/aws/kms.py +173 -201
- cartography/intel/aws/lambda_function.py +272 -189
- cartography/intel/aws/organizations.py +10 -9
- cartography/intel/aws/permission_relationships.py +10 -20
- cartography/intel/aws/rds.py +337 -446
- cartography/intel/aws/redshift.py +9 -4
- cartography/intel/aws/resourcegroupstaggingapi.py +78 -19
- cartography/intel/aws/resources.py +18 -0
- cartography/intel/aws/route53.py +386 -332
- cartography/intel/aws/s3.py +322 -14
- cartography/intel/aws/secretsmanager.py +81 -49
- cartography/intel/aws/securityhub.py +3 -1
- cartography/intel/aws/sns.py +62 -2
- cartography/intel/aws/sqs.py +36 -90
- cartography/intel/aws/ssm.py +3 -5
- cartography/intel/azure/__init__.py +202 -48
- cartography/intel/azure/aks.py +175 -0
- cartography/intel/azure/app_service.py +105 -0
- cartography/intel/azure/compute.py +59 -112
- cartography/intel/azure/container_instances.py +95 -0
- cartography/intel/azure/cosmosdb.py +222 -361
- cartography/intel/azure/data_factory.py +85 -0
- cartography/intel/azure/data_factory_dataset.py +128 -0
- cartography/intel/azure/data_factory_linked_service.py +119 -0
- cartography/intel/azure/data_factory_pipeline.py +142 -0
- cartography/intel/azure/data_lake.py +124 -0
- cartography/intel/azure/event_grid.py +94 -0
- cartography/intel/azure/functions.py +124 -0
- cartography/intel/azure/load_balancers.py +263 -0
- cartography/intel/azure/logic_apps.py +101 -0
- cartography/intel/azure/monitor.py +105 -0
- cartography/intel/azure/network.py +467 -0
- cartography/intel/azure/permission_relationships.py +466 -0
- cartography/intel/azure/rbac.py +309 -0
- cartography/intel/azure/resource_groups.py +82 -0
- cartography/intel/azure/security_center.py +106 -0
- cartography/intel/azure/sql.py +145 -292
- cartography/intel/azure/storage.py +185 -262
- cartography/intel/azure/subscription.py +21 -43
- cartography/intel/azure/tenant.py +39 -30
- cartography/intel/azure/util/common.py +13 -0
- cartography/intel/azure/util/credentials.py +49 -174
- cartography/intel/azure/util/tag.py +41 -0
- cartography/intel/create_indexes.py +2 -1
- cartography/intel/crowdstrike/spotlight.py +5 -2
- cartography/intel/dns.py +5 -2
- cartography/intel/entra/__init__.py +100 -1
- cartography/intel/entra/app_role_assignments.py +284 -0
- cartography/intel/entra/applications.py +182 -0
- cartography/intel/entra/federation/__init__.py +0 -0
- cartography/intel/entra/federation/aws_identity_center.py +77 -0
- cartography/intel/entra/groups.py +198 -0
- cartography/intel/entra/ou.py +48 -24
- cartography/intel/entra/service_principals.py +217 -0
- cartography/intel/entra/users.py +105 -57
- cartography/intel/gcp/__init__.py +334 -396
- cartography/intel/gcp/bigtable_app_profile.py +101 -0
- cartography/intel/gcp/bigtable_backup.py +91 -0
- cartography/intel/gcp/bigtable_cluster.py +93 -0
- cartography/intel/gcp/bigtable_instance.py +86 -0
- cartography/intel/gcp/bigtable_table.py +87 -0
- cartography/intel/gcp/cai.py +292 -0
- cartography/intel/gcp/clients.py +112 -0
- cartography/intel/gcp/compute.py +128 -119
- cartography/intel/gcp/crm/__init__.py +0 -0
- cartography/intel/gcp/crm/folders.py +114 -0
- cartography/intel/gcp/crm/orgs.py +70 -0
- cartography/intel/gcp/crm/projects.py +120 -0
- cartography/intel/gcp/dns.py +83 -169
- cartography/intel/gcp/gke.py +72 -113
- cartography/intel/gcp/iam.py +111 -91
- cartography/intel/gcp/permission_relationships.py +394 -0
- cartography/intel/gcp/policy_bindings.py +225 -0
- cartography/intel/gcp/storage.py +75 -159
- cartography/intel/github/__init__.py +62 -25
- cartography/intel/github/commits.py +423 -0
- cartography/intel/github/repos.py +463 -85
- cartography/intel/github/teams.py +3 -3
- cartography/intel/github/users.py +5 -0
- cartography/intel/github/util.py +12 -0
- cartography/intel/googleworkspace/__init__.py +193 -0
- cartography/intel/googleworkspace/devices.py +254 -0
- cartography/intel/googleworkspace/groups.py +568 -0
- cartography/intel/googleworkspace/oauth_apps.py +259 -0
- cartography/intel/googleworkspace/tenant.py +85 -0
- cartography/intel/googleworkspace/users.py +138 -0
- cartography/intel/gsuite/__init__.py +17 -9
- cartography/intel/gsuite/groups.py +291 -0
- cartography/intel/gsuite/users.py +142 -0
- cartography/intel/jamf/computers.py +7 -1
- cartography/intel/keycloak/__init__.py +153 -0
- cartography/intel/keycloak/authenticationexecutions.py +322 -0
- cartography/intel/keycloak/authenticationflows.py +77 -0
- cartography/intel/keycloak/clients.py +187 -0
- cartography/intel/keycloak/groups.py +126 -0
- cartography/intel/keycloak/identityproviders.py +94 -0
- cartography/intel/keycloak/organizations.py +163 -0
- cartography/intel/keycloak/realms.py +61 -0
- cartography/intel/keycloak/roles.py +202 -0
- cartography/intel/keycloak/scopes.py +73 -0
- cartography/intel/keycloak/users.py +70 -0
- cartography/intel/keycloak/util.py +47 -0
- cartography/intel/kubernetes/__init__.py +60 -14
- cartography/intel/kubernetes/clusters.py +86 -0
- cartography/intel/kubernetes/eks.py +402 -0
- cartography/intel/kubernetes/namespaces.py +59 -57
- cartography/intel/kubernetes/pods.py +168 -75
- cartography/intel/kubernetes/rbac.py +597 -0
- cartography/intel/kubernetes/secrets.py +95 -45
- cartography/intel/kubernetes/services.py +131 -67
- cartography/intel/kubernetes/util.py +142 -14
- cartography/intel/oci/iam.py +23 -9
- cartography/intel/oci/organizations.py +3 -1
- cartography/intel/oci/utils.py +28 -5
- cartography/intel/okta/applications.py +15 -5
- cartography/intel/okta/awssaml.py +14 -10
- cartography/intel/okta/factors.py +3 -1
- cartography/intel/okta/groups.py +5 -2
- cartography/intel/okta/organization.py +3 -1
- cartography/intel/okta/origins.py +3 -1
- cartography/intel/okta/roles.py +5 -2
- cartography/intel/okta/users.py +10 -2
- cartography/intel/ontology/__init__.py +44 -0
- cartography/intel/ontology/devices.py +54 -0
- cartography/intel/ontology/users.py +54 -0
- cartography/intel/ontology/utils.py +176 -0
- cartography/intel/pagerduty/escalation_policies.py +13 -6
- cartography/intel/pagerduty/schedules.py +9 -4
- cartography/intel/pagerduty/services.py +7 -3
- cartography/intel/pagerduty/teams.py +5 -2
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/scaleway/__init__.py +127 -0
- cartography/intel/scaleway/iam/__init__.py +0 -0
- cartography/intel/scaleway/iam/apikeys.py +71 -0
- cartography/intel/scaleway/iam/applications.py +71 -0
- cartography/intel/scaleway/iam/groups.py +71 -0
- cartography/intel/scaleway/iam/users.py +71 -0
- cartography/intel/scaleway/instances/__init__.py +0 -0
- cartography/intel/scaleway/instances/flexibleips.py +86 -0
- cartography/intel/scaleway/instances/instances.py +92 -0
- cartography/intel/scaleway/projects.py +79 -0
- cartography/intel/scaleway/storage/__init__.py +0 -0
- cartography/intel/scaleway/storage/snapshots.py +86 -0
- cartography/intel/scaleway/storage/volumes.py +84 -0
- cartography/intel/scaleway/utils.py +37 -0
- cartography/intel/sentinelone/__init__.py +75 -0
- cartography/intel/sentinelone/account.py +140 -0
- cartography/intel/sentinelone/agent.py +139 -0
- cartography/intel/sentinelone/api.py +124 -0
- cartography/intel/sentinelone/application.py +248 -0
- cartography/intel/sentinelone/cve.py +119 -0
- cartography/intel/sentinelone/utils.py +28 -0
- cartography/intel/slack/__init__.py +78 -0
- cartography/intel/slack/channels.py +80 -0
- cartography/intel/slack/groups.py +90 -0
- cartography/intel/slack/teams.py +65 -0
- cartography/intel/slack/users.py +57 -0
- cartography/intel/slack/utils.py +29 -0
- cartography/intel/spacelift/__init__.py +161 -0
- cartography/intel/spacelift/account.py +73 -0
- cartography/intel/spacelift/ec2_ownership.py +280 -0
- cartography/intel/spacelift/runs.py +463 -0
- cartography/intel/spacelift/spaces.py +112 -0
- cartography/intel/spacelift/stacks.py +119 -0
- cartography/intel/spacelift/util.py +122 -0
- cartography/intel/spacelift/workerpools.py +131 -0
- cartography/intel/spacelift/workers.py +128 -0
- cartography/intel/trivy/__init__.py +272 -0
- cartography/intel/trivy/scanner.py +386 -0
- cartography/models/airbyte/__init__.py +0 -0
- cartography/models/airbyte/connection.py +138 -0
- cartography/models/airbyte/destination.py +75 -0
- cartography/models/airbyte/organization.py +19 -0
- cartography/models/airbyte/source.py +75 -0
- cartography/models/airbyte/stream.py +74 -0
- cartography/models/airbyte/tag.py +69 -0
- cartography/models/airbyte/user.py +115 -0
- cartography/models/airbyte/workspace.py +46 -0
- cartography/models/anthropic/apikey.py +4 -0
- cartography/models/anthropic/user.py +4 -0
- cartography/models/aws/acm/__init__.py +0 -0
- cartography/models/aws/acm/certificate.py +75 -0
- cartography/models/aws/apigateway/__init__.py +0 -0
- cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
- cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
- cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
- cartography/models/aws/apigatewayv2/__init__.py +0 -0
- cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
- cartography/models/aws/cloudtrail/management_events.py +153 -0
- cartography/models/aws/cloudtrail/trail.py +45 -0
- cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
- cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
- cartography/models/aws/codebuild/__init__.py +0 -0
- cartography/models/aws/codebuild/project.py +49 -0
- cartography/models/aws/cognito/__init__.py +0 -0
- cartography/models/aws/cognito/identity_pool.py +70 -0
- cartography/models/aws/cognito/user_pool.py +47 -0
- cartography/models/aws/dynamodb/tables.py +2 -0
- cartography/models/aws/ec2/instances.py +25 -1
- cartography/models/aws/ec2/networkinterfaces.py +4 -0
- cartography/models/aws/ec2/security_group_rules.py +109 -0
- cartography/models/aws/ec2/security_groups.py +90 -0
- cartography/models/aws/ec2/snapshots.py +58 -0
- cartography/models/aws/ec2/subnet_instance.py +2 -0
- cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
- cartography/models/aws/ec2/subnets.py +65 -0
- cartography/models/aws/ec2/volumes.py +20 -0
- cartography/models/aws/ec2/vpc.py +46 -0
- cartography/models/aws/ec2/vpc_cidr.py +102 -0
- cartography/models/aws/ec2/vpc_peering.py +157 -0
- cartography/models/aws/ecr/__init__.py +0 -0
- cartography/models/aws/ecr/image.py +146 -0
- cartography/models/aws/ecr/image_layer.py +107 -0
- cartography/models/aws/ecr/repository.py +72 -0
- cartography/models/aws/ecr/repository_image.py +95 -0
- cartography/models/aws/ecs/__init__.py +0 -0
- cartography/models/aws/ecs/clusters.py +64 -0
- cartography/models/aws/ecs/container_definitions.py +93 -0
- cartography/models/aws/ecs/container_instances.py +84 -0
- cartography/models/aws/ecs/containers.py +101 -0
- cartography/models/aws/ecs/services.py +134 -0
- cartography/models/aws/ecs/task_definitions.py +135 -0
- cartography/models/aws/ecs/tasks.py +134 -0
- cartography/models/aws/efs/access_point.py +77 -0
- cartography/models/aws/efs/file_system.py +60 -0
- cartography/models/aws/efs/mount_target.py +29 -2
- cartography/models/aws/elasticache/__init__.py +0 -0
- cartography/models/aws/elasticache/cluster.py +65 -0
- cartography/models/aws/elasticache/topic.py +67 -0
- cartography/models/aws/eventbridge/__init__.py +0 -0
- cartography/models/aws/eventbridge/rule.py +77 -0
- cartography/models/aws/eventbridge/target.py +71 -0
- cartography/models/aws/glue/__init__.py +0 -0
- cartography/models/aws/glue/connection.py +51 -0
- cartography/models/aws/glue/job.py +69 -0
- cartography/models/aws/guardduty/__init__.py +1 -0
- cartography/models/aws/guardduty/detectors.py +50 -0
- cartography/models/aws/guardduty/findings.py +121 -0
- cartography/models/aws/iam/access_key.py +103 -0
- cartography/models/aws/iam/account_role.py +24 -0
- cartography/models/aws/iam/federated_principal.py +60 -0
- cartography/models/aws/iam/group.py +60 -0
- cartography/models/aws/iam/group_membership.py +27 -0
- cartography/models/aws/iam/inline_policy.py +78 -0
- cartography/models/aws/iam/managed_policy.py +51 -0
- cartography/models/aws/iam/policy_statement.py +57 -0
- cartography/models/aws/iam/role.py +83 -0
- cartography/models/aws/iam/root_principal.py +52 -0
- cartography/models/aws/iam/service_principal.py +30 -0
- cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
- cartography/models/aws/iam/user.py +59 -0
- cartography/models/aws/identitycenter/awsidentitycenter.py +1 -0
- cartography/models/aws/identitycenter/awspermissionset.py +70 -0
- cartography/models/aws/identitycenter/awssogroup.py +70 -0
- cartography/models/aws/identitycenter/awsssouser.py +49 -9
- cartography/models/aws/inspector/findings.py +37 -0
- cartography/models/aws/inspector/packages.py +1 -31
- cartography/models/aws/kms/__init__.py +0 -0
- cartography/models/aws/kms/aliases.py +86 -0
- cartography/models/aws/kms/grants.py +65 -0
- cartography/models/aws/kms/keys.py +88 -0
- cartography/models/aws/lambda_function/__init__.py +0 -0
- cartography/models/aws/lambda_function/alias.py +74 -0
- cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
- cartography/models/aws/lambda_function/lambda_function.py +91 -0
- cartography/models/aws/lambda_function/layer.py +72 -0
- cartography/models/aws/rds/__init__.py +0 -0
- cartography/models/aws/rds/cluster.py +91 -0
- cartography/models/aws/rds/event_subscription.py +146 -0
- cartography/models/aws/rds/instance.py +156 -0
- cartography/models/aws/rds/snapshot.py +108 -0
- cartography/models/aws/rds/subnet_group.py +101 -0
- cartography/models/aws/route53/__init__.py +0 -0
- cartography/models/aws/route53/dnsrecord.py +235 -0
- cartography/models/aws/route53/nameserver.py +63 -0
- cartography/models/aws/route53/subzone.py +40 -0
- cartography/models/aws/route53/zone.py +47 -0
- cartography/models/aws/s3/notification.py +24 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/aws/secretsmanager/secret_version.py +0 -2
- cartography/models/aws/sns/topic_subscription.py +74 -0
- cartography/models/aws/sqs/__init__.py +0 -0
- cartography/models/aws/sqs/queue.py +89 -0
- cartography/models/azure/__init__.py +0 -0
- cartography/models/azure/aks_cluster.py +54 -0
- cartography/models/azure/aks_nodepool.py +54 -0
- cartography/models/azure/app_service.py +59 -0
- cartography/models/azure/container_instance.py +57 -0
- cartography/models/azure/cosmosdb/__init__.py +0 -0
- cartography/models/azure/cosmosdb/account.py +77 -0
- cartography/models/azure/cosmosdb/accountfailoverpolicy.py +77 -0
- cartography/models/azure/cosmosdb/cassandrakeyspace.py +82 -0
- cartography/models/azure/cosmosdb/cassandratable.py +81 -0
- cartography/models/azure/cosmosdb/corspolicy.py +74 -0
- cartography/models/azure/cosmosdb/dblocation.py +120 -0
- cartography/models/azure/cosmosdb/mongodbcollection.py +82 -0
- cartography/models/azure/cosmosdb/mongodbdatabase.py +78 -0
- cartography/models/azure/cosmosdb/privateendpointconnection.py +81 -0
- cartography/models/azure/cosmosdb/sqlcontainer.py +88 -0
- cartography/models/azure/cosmosdb/sqldatabase.py +78 -0
- cartography/models/azure/cosmosdb/tableresource.py +76 -0
- cartography/models/azure/cosmosdb/virtualnetworkrule.py +78 -0
- cartography/models/azure/data_factory/__init__.py +0 -0
- cartography/models/azure/data_factory/data_factory.py +51 -0
- cartography/models/azure/data_factory/data_factory_dataset.py +94 -0
- cartography/models/azure/data_factory/data_factory_linked_service.py +78 -0
- cartography/models/azure/data_factory/data_factory_pipeline.py +93 -0
- cartography/models/azure/data_lake_filesystem.py +51 -0
- cartography/models/azure/event_grid_topic.py +57 -0
- cartography/models/azure/function_app.py +59 -0
- cartography/models/azure/load_balancer/__init__.py +0 -0
- cartography/models/azure/load_balancer/load_balancer.py +49 -0
- cartography/models/azure/load_balancer/load_balancer_backend_pool.py +73 -0
- cartography/models/azure/load_balancer/load_balancer_frontend_ip.py +75 -0
- cartography/models/azure/load_balancer/load_balancer_inbound_nat_rule.py +78 -0
- cartography/models/azure/load_balancer/load_balancer_rule.py +108 -0
- cartography/models/azure/logic_apps.py +56 -0
- cartography/models/azure/monitor.py +54 -0
- cartography/models/azure/network_interface.py +112 -0
- cartography/models/azure/network_security_group.py +50 -0
- cartography/models/azure/permission_relationships.py +60 -0
- cartography/models/azure/principal.py +41 -0
- cartography/models/azure/public_ip_address.py +50 -0
- cartography/models/azure/rbac.py +268 -0
- cartography/models/azure/resource_groups.py +52 -0
- cartography/models/azure/security_center.py +50 -0
- cartography/models/azure/sql/__init__.py +0 -0
- cartography/models/azure/sql/databasethreatdetectionpolicy.py +85 -0
- cartography/models/azure/sql/elasticpool.py +77 -0
- cartography/models/azure/sql/failovergroup.py +73 -0
- cartography/models/azure/sql/recoverabledatabase.py +75 -0
- cartography/models/azure/sql/replicationlink.py +81 -0
- cartography/models/azure/sql/restorabledroppeddatabase.py +82 -0
- cartography/models/azure/sql/restorepoint.py +74 -0
- cartography/models/azure/sql/serveradadministrator.py +74 -0
- cartography/models/azure/sql/serverdnsalias.py +71 -0
- cartography/models/azure/sql/sqldatabase.py +85 -0
- cartography/models/azure/sql/sqlserver.py +50 -0
- cartography/models/azure/sql/transparentdataencryption.py +76 -0
- cartography/models/azure/storage/__init__.py +0 -0
- cartography/models/azure/storage/account.py +59 -0
- cartography/models/azure/storage/blobcontainer.py +85 -0
- cartography/models/azure/storage/blobservice.py +71 -0
- cartography/models/azure/storage/fileservice.py +71 -0
- cartography/models/azure/storage/fileshare.py +82 -0
- cartography/models/azure/storage/queue.py +71 -0
- cartography/models/azure/storage/queueservice.py +73 -0
- cartography/models/azure/storage/table.py +72 -0
- cartography/models/azure/storage/tableservice.py +73 -0
- cartography/models/azure/subnet.py +101 -0
- cartography/models/azure/subscription.py +47 -0
- cartography/models/azure/tags/__init__.py +0 -0
- cartography/models/azure/tags/storage_tag.py +40 -0
- cartography/models/azure/tags/tag.py +37 -0
- cartography/models/azure/tenant.py +17 -0
- cartography/models/azure/virtual_network.py +49 -0
- cartography/models/azure/vm/__init__.py +0 -0
- cartography/models/azure/vm/datadisk.py +80 -0
- cartography/models/azure/vm/disk.py +55 -0
- cartography/models/azure/vm/snapshot.py +56 -0
- cartography/models/azure/vm/virtualmachine.py +59 -0
- cartography/models/bigfix/bigfix_computer.py +1 -1
- cartography/models/cloudflare/member.py +4 -0
- cartography/models/core/common.py +1 -0
- cartography/models/core/nodes.py +15 -2
- cartography/models/core/relationships.py +44 -0
- cartography/models/crowdstrike/hosts.py +1 -1
- cartography/models/digitalocean/droplet.py +2 -0
- cartography/models/duo/endpoint.py +1 -1
- cartography/models/duo/phone.py +2 -2
- cartography/models/duo/user.py +4 -0
- cartography/models/entra/app_role_assignment.py +115 -0
- cartography/models/entra/application.py +49 -0
- cartography/models/entra/entra_user_to_aws_sso.py +41 -0
- cartography/models/entra/group.py +117 -0
- cartography/models/entra/service_principal.py +104 -0
- cartography/models/entra/user.py +42 -51
- cartography/models/gcp/__init__.py +0 -0
- cartography/models/gcp/bigtable/__init__.py +0 -0
- cartography/models/gcp/bigtable/app_profile.py +94 -0
- cartography/models/gcp/bigtable/backup.py +91 -0
- cartography/models/gcp/bigtable/cluster.py +73 -0
- cartography/models/gcp/bigtable/instance.py +52 -0
- cartography/models/gcp/bigtable/table.py +69 -0
- cartography/models/gcp/compute/__init__.py +0 -0
- cartography/models/gcp/compute/subnet.py +74 -0
- cartography/models/gcp/compute/vpc.py +50 -0
- cartography/models/gcp/crm/__init__.py +0 -0
- cartography/models/gcp/crm/folders.py +98 -0
- cartography/models/gcp/crm/organizations.py +21 -0
- cartography/models/gcp/crm/projects.py +100 -0
- cartography/models/gcp/dns.py +109 -0
- cartography/models/gcp/gke.py +69 -0
- cartography/models/gcp/iam.py +3 -0
- cartography/models/gcp/permission_relationships.py +61 -0
- cartography/models/gcp/policy_bindings.py +93 -0
- cartography/models/gcp/storage/__init__.py +0 -0
- cartography/models/gcp/storage/bucket.py +119 -0
- cartography/models/github/commits.py +63 -0
- cartography/models/github/dependencies.py +73 -0
- cartography/models/github/manifests.py +49 -0
- cartography/models/github/users.py +10 -0
- cartography/models/googleworkspace/__init__.py +0 -0
- cartography/models/googleworkspace/device.py +132 -0
- cartography/models/googleworkspace/group.py +382 -0
- cartography/models/googleworkspace/oauth_app.py +124 -0
- cartography/models/googleworkspace/tenant.py +30 -0
- cartography/models/googleworkspace/user.py +113 -0
- cartography/models/gsuite/__init__.py +0 -0
- cartography/models/gsuite/group.py +218 -0
- cartography/models/gsuite/tenant.py +29 -0
- cartography/models/gsuite/user.py +107 -0
- cartography/models/kandji/device.py +1 -2
- cartography/models/keycloak/__init__.py +0 -0
- cartography/models/keycloak/authenticationexecution.py +160 -0
- cartography/models/keycloak/authenticationflow.py +54 -0
- cartography/models/keycloak/client.py +179 -0
- cartography/models/keycloak/group.py +101 -0
- cartography/models/keycloak/identityprovider.py +89 -0
- cartography/models/keycloak/organization.py +116 -0
- cartography/models/keycloak/organizationdomain.py +73 -0
- cartography/models/keycloak/realm.py +173 -0
- cartography/models/keycloak/role.py +126 -0
- cartography/models/keycloak/scope.py +73 -0
- cartography/models/keycloak/user.py +55 -0
- cartography/models/kubernetes/__init__.py +0 -0
- cartography/models/kubernetes/clusterrolebindings.py +138 -0
- cartography/models/kubernetes/clusterroles.py +52 -0
- cartography/models/kubernetes/clusters.py +26 -0
- cartography/models/kubernetes/containers.py +133 -0
- cartography/models/kubernetes/groups.py +107 -0
- cartography/models/kubernetes/namespaces.py +51 -0
- cartography/models/kubernetes/oidc.py +51 -0
- cartography/models/kubernetes/pods.py +80 -0
- cartography/models/kubernetes/rolebindings.py +159 -0
- cartography/models/kubernetes/roles.py +76 -0
- cartography/models/kubernetes/secrets.py +79 -0
- cartography/models/kubernetes/serviceaccounts.py +77 -0
- cartography/models/kubernetes/services.py +108 -0
- cartography/models/kubernetes/users.py +105 -0
- cartography/models/lastpass/user.py +4 -0
- cartography/models/ontology/__init__.py +0 -0
- cartography/models/ontology/device.py +137 -0
- cartography/models/ontology/mapping/__init__.py +76 -0
- cartography/models/ontology/mapping/data/__init__.py +0 -0
- cartography/models/ontology/mapping/data/apikeys.py +93 -0
- cartography/models/ontology/mapping/data/computeinstance.py +95 -0
- cartography/models/ontology/mapping/data/containers.py +88 -0
- cartography/models/ontology/mapping/data/databases.py +182 -0
- cartography/models/ontology/mapping/data/devices.py +194 -0
- cartography/models/ontology/mapping/data/thirdpartyapps.py +140 -0
- cartography/models/ontology/mapping/data/useraccounts.py +416 -0
- cartography/models/ontology/mapping/data/users.py +63 -0
- cartography/models/ontology/mapping/specs.py +85 -0
- cartography/models/ontology/user.py +51 -0
- cartography/models/openai/adminapikey.py +4 -0
- cartography/models/openai/apikey.py +4 -0
- cartography/models/openai/user.py +4 -0
- cartography/models/scaleway/__init__.py +0 -0
- cartography/models/scaleway/iam/__init__.py +0 -0
- cartography/models/scaleway/iam/apikey.py +100 -0
- cartography/models/scaleway/iam/application.py +52 -0
- cartography/models/scaleway/iam/group.py +95 -0
- cartography/models/scaleway/iam/user.py +64 -0
- cartography/models/scaleway/instance/__init__.py +0 -0
- cartography/models/scaleway/instance/flexibleip.py +52 -0
- cartography/models/scaleway/instance/instance.py +120 -0
- cartography/models/scaleway/organization.py +19 -0
- cartography/models/scaleway/project.py +48 -0
- cartography/models/scaleway/storage/__init__.py +0 -0
- cartography/models/scaleway/storage/snapshot.py +78 -0
- cartography/models/scaleway/storage/volume.py +51 -0
- cartography/models/sentinelone/__init__.py +1 -0
- cartography/models/sentinelone/account.py +40 -0
- cartography/models/sentinelone/agent.py +50 -0
- cartography/models/sentinelone/application.py +44 -0
- cartography/models/sentinelone/application_version.py +96 -0
- cartography/models/sentinelone/cve.py +73 -0
- cartography/models/slack/__init__.py +0 -0
- cartography/models/slack/channels.py +92 -0
- cartography/models/slack/group.py +129 -0
- cartography/models/slack/team.py +22 -0
- cartography/models/slack/user.py +62 -0
- cartography/models/snipeit/asset.py +2 -0
- cartography/models/snipeit/user.py +4 -0
- cartography/models/spacelift/__init__.py +0 -0
- cartography/models/spacelift/cloudtrailevent.py +120 -0
- cartography/models/spacelift/run.py +162 -0
- cartography/models/spacelift/space.py +131 -0
- cartography/models/spacelift/spaceliftaccount.py +31 -0
- cartography/models/spacelift/spaceliftgitcommit.py +157 -0
- cartography/models/spacelift/stack.py +96 -0
- cartography/models/spacelift/user.py +63 -0
- cartography/models/spacelift/worker.py +97 -0
- cartography/models/spacelift/workerpool.py +90 -0
- cartography/models/tailscale/device.py +2 -1
- cartography/models/tailscale/user.py +6 -1
- cartography/models/trivy/__init__.py +0 -0
- cartography/models/trivy/findings.py +66 -0
- cartography/models/trivy/fix.py +66 -0
- cartography/models/trivy/package.py +71 -0
- cartography/rules/README.md +1 -0
- cartography/rules/__init__.py +0 -0
- cartography/rules/cli.py +261 -0
- cartography/rules/data/__init__.py +0 -0
- cartography/rules/data/rules/__init__.py +46 -0
- cartography/rules/data/rules/cloud_security_product_deactivated.py +49 -0
- cartography/rules/data/rules/compute_instance_exposed.py +51 -0
- cartography/rules/data/rules/database_instance_exposed.py +53 -0
- cartography/rules/data/rules/delegation_boundary_modifiable.py +90 -0
- cartography/rules/data/rules/identity_administration_privileges.py +100 -0
- cartography/rules/data/rules/inactive_user_active_accounts.py +48 -0
- cartography/rules/data/rules/malicious_npm_dependencies_shai_hulud.py +2222 -0
- cartography/rules/data/rules/mfa_missing.py +46 -0
- cartography/rules/data/rules/object_storage_public.py +100 -0
- cartography/rules/data/rules/policy_administration_privileges.py +104 -0
- cartography/rules/data/rules/unmanaged_accounts.py +43 -0
- cartography/rules/data/rules/workload_identity_admin_capabilities.py +193 -0
- cartography/rules/formatters.py +108 -0
- cartography/rules/runners.py +216 -0
- cartography/rules/spec/__init__.py +0 -0
- cartography/rules/spec/model.py +267 -0
- cartography/rules/spec/result.py +38 -0
- cartography/sync.py +25 -5
- cartography/util.py +101 -31
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/METADATA +61 -22
- cartography-0.123.0.dist-info/RECORD +856 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
- cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
- cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
- cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json +0 -24
- cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
- cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
- cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
- cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_import_snapshots_cleanup.json +0 -30
- cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
- cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
- cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json +0 -15
- cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_sql_database_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_table_resources_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_database_account_cleanup.json +0 -85
- cartography/data/jobs/cleanup/azure_import_disks_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_import_snapshots_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_import_virtual_machines_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_sql_server_cleanup.json +0 -125
- cartography/data/jobs/cleanup/azure_storage_account_cleanup.json +0 -95
- cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json +0 -14
- cartography/data/jobs/cleanup/azure_tenant_cleanup.json +0 -9
- cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
- cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
- cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
- cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
- cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
- cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
- cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
- cartography/intel/gcp/crm.py +0 -355
- cartography/intel/gsuite/api.py +0 -342
- cartography-0.104.0rc2.dist-info/RECORD +0 -455
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- /cartography/models/aws/{apigateway.py → apigateway/apigateway.py} +0 -0
- /cartography/models/aws/{apigatewaycertificate.py → apigateway/apigatewaycertificate.py} +0 -0
- /cartography/models/aws/{apigatewayresource.py → apigateway/apigatewayresource.py} +0 -0
- /cartography/models/aws/{apigatewaystage.py → apigateway/apigatewaystage.py} +0 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/WHEEL +0 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.104.0rc2.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:
|
|
24
|
-
extra_node_labels:
|
|
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:
|
|
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),
|
|
@@ -69,7 +364,7 @@ def _build_node_properties_statement(
|
|
|
69
364
|
|
|
70
365
|
def _build_rel_properties_statement(
|
|
71
366
|
rel_var: str,
|
|
72
|
-
rel_property_map:
|
|
367
|
+
rel_property_map: dict[str, PropertyRef] | None = None,
|
|
73
368
|
) -> str:
|
|
74
369
|
"""
|
|
75
370
|
Generate a Neo4j clause that sets relationship properties using the given mapping of attribute names to
|
|
@@ -77,7 +372,7 @@ def _build_rel_properties_statement(
|
|
|
77
372
|
|
|
78
373
|
In this code example:
|
|
79
374
|
|
|
80
|
-
rel_property_map:
|
|
375
|
+
rel_property_map: dict[str, PropertyRef] = {
|
|
81
376
|
'rel_prop_1': PropertyRef("Prop1"),
|
|
82
377
|
'rel_prop_2': PropertyRef("Prop2", static=True),
|
|
83
378
|
}
|
|
@@ -109,10 +404,10 @@ def _build_rel_properties_statement(
|
|
|
109
404
|
return set_clause
|
|
110
405
|
|
|
111
406
|
|
|
112
|
-
def _build_match_clause(matcher: TargetNodeMatcher) -> str:
|
|
407
|
+
def _build_match_clause(matcher: TargetNodeMatcher | SourceNodeMatcher) -> str:
|
|
113
408
|
"""
|
|
114
409
|
Generate a Neo4j match statement on one or more keys and values for a given node.
|
|
115
|
-
:param matcher: A TargetNodeMatcher object
|
|
410
|
+
:param matcher: A TargetNodeMatcher or SourceNodeMatcher object
|
|
116
411
|
:return: a Neo4j match clause
|
|
117
412
|
"""
|
|
118
413
|
match = Template("$Key: $PropRef")
|
|
@@ -173,13 +468,13 @@ def _build_where_clause_for_rel_match(
|
|
|
173
468
|
|
|
174
469
|
def _asdict_with_validate_relprops(
|
|
175
470
|
link: CartographyRelSchema,
|
|
176
|
-
) ->
|
|
471
|
+
) -> dict[str, PropertyRef]:
|
|
177
472
|
"""
|
|
178
473
|
Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
|
|
179
474
|
isn't always caught by IDEs.
|
|
180
475
|
"""
|
|
181
476
|
try:
|
|
182
|
-
rel_props_as_dict:
|
|
477
|
+
rel_props_as_dict: dict[str, PropertyRef] = asdict(link.properties)
|
|
183
478
|
except TypeError as e:
|
|
184
479
|
if (
|
|
185
480
|
e.args
|
|
@@ -197,7 +492,7 @@ def _asdict_with_validate_relprops(
|
|
|
197
492
|
|
|
198
493
|
|
|
199
494
|
def _build_attach_sub_resource_statement(
|
|
200
|
-
sub_resource_link:
|
|
495
|
+
sub_resource_link: CartographyRelSchema | None = None,
|
|
201
496
|
) -> str:
|
|
202
497
|
"""
|
|
203
498
|
Generates a Neo4j statement to attach a sub resource to a node. A 'sub resource' is a term we made up to describe
|
|
@@ -222,6 +517,8 @@ def _build_attach_sub_resource_statement(
|
|
|
222
517
|
$RelMergeClause
|
|
223
518
|
ON CREATE SET r.firstseen = timestamp()
|
|
224
519
|
SET
|
|
520
|
+
r._module_name = "$module_name",
|
|
521
|
+
r._module_version = "$module_version",
|
|
225
522
|
$set_rel_properties_statement
|
|
226
523
|
""",
|
|
227
524
|
)
|
|
@@ -235,7 +532,7 @@ def _build_attach_sub_resource_statement(
|
|
|
235
532
|
SubResourceRelLabel=sub_resource_link.rel_label,
|
|
236
533
|
)
|
|
237
534
|
|
|
238
|
-
rel_props_as_dict:
|
|
535
|
+
rel_props_as_dict: dict[str, PropertyRef] = _asdict_with_validate_relprops(
|
|
239
536
|
sub_resource_link,
|
|
240
537
|
)
|
|
241
538
|
|
|
@@ -243,6 +540,8 @@ def _build_attach_sub_resource_statement(
|
|
|
243
540
|
SubResourceLabel=sub_resource_link.target_node_label,
|
|
244
541
|
MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
|
|
245
542
|
RelMergeClause=rel_merge_clause,
|
|
543
|
+
module_name=_get_module_from_schema(sub_resource_link),
|
|
544
|
+
module_version=_get_cartography_version(),
|
|
246
545
|
SubResourceRelLabel=sub_resource_link.rel_label,
|
|
247
546
|
set_rel_properties_statement=_build_rel_properties_statement(
|
|
248
547
|
"r",
|
|
@@ -253,7 +552,7 @@ def _build_attach_sub_resource_statement(
|
|
|
253
552
|
|
|
254
553
|
|
|
255
554
|
def _build_attach_additional_links_statement(
|
|
256
|
-
additional_relationships:
|
|
555
|
+
additional_relationships: OtherRelationships | None = None,
|
|
257
556
|
) -> str:
|
|
258
557
|
"""
|
|
259
558
|
Generates a Neo4j statement to attach one or more CartographyRelSchemas to node(s) previously mentioned in the
|
|
@@ -277,6 +576,8 @@ def _build_attach_additional_links_statement(
|
|
|
277
576
|
$RelMerge
|
|
278
577
|
ON CREATE SET $rel_var.firstseen = timestamp()
|
|
279
578
|
SET
|
|
579
|
+
$rel_var._module_name = "$module_name",
|
|
580
|
+
$rel_var._module_version = "$module_version",
|
|
280
581
|
$set_rel_properties_statement
|
|
281
582
|
""",
|
|
282
583
|
)
|
|
@@ -311,6 +612,8 @@ def _build_attach_additional_links_statement(
|
|
|
311
612
|
node_var=node_var,
|
|
312
613
|
rel_var=rel_var,
|
|
313
614
|
RelMerge=rel_merge,
|
|
615
|
+
module_name=_get_module_from_schema(link),
|
|
616
|
+
module_version=_get_cartography_version(),
|
|
314
617
|
set_rel_properties_statement=_build_rel_properties_statement(
|
|
315
618
|
rel_var,
|
|
316
619
|
rel_props_as_dict,
|
|
@@ -322,8 +625,8 @@ def _build_attach_additional_links_statement(
|
|
|
322
625
|
|
|
323
626
|
|
|
324
627
|
def _build_attach_relationships_statement(
|
|
325
|
-
sub_resource_relationship:
|
|
326
|
-
other_relationships:
|
|
628
|
+
sub_resource_relationship: CartographyRelSchema | None,
|
|
629
|
+
other_relationships: OtherRelationships | None,
|
|
327
630
|
) -> str:
|
|
328
631
|
"""
|
|
329
632
|
Use Neo4j subqueries to attach sub resource and/or other relationships.
|
|
@@ -381,8 +684,8 @@ def rel_present_on_node_schema(
|
|
|
381
684
|
|
|
382
685
|
def filter_selected_relationships(
|
|
383
686
|
node_schema: CartographyNodeSchema,
|
|
384
|
-
selected_relationships:
|
|
385
|
-
) ->
|
|
687
|
+
selected_relationships: set[CartographyRelSchema],
|
|
688
|
+
) -> tuple[CartographyRelSchema | None, OtherRelationships | None]:
|
|
386
689
|
"""
|
|
387
690
|
Ensures that selected relationships specified to build_ingestion_query() are actually present on
|
|
388
691
|
node_schema.sub_resource_relationship and node_schema.other_relationships.
|
|
@@ -425,7 +728,7 @@ def filter_selected_relationships(
|
|
|
425
728
|
|
|
426
729
|
def build_ingestion_query(
|
|
427
730
|
node_schema: CartographyNodeSchema,
|
|
428
|
-
selected_relationships:
|
|
731
|
+
selected_relationships: set[CartographyRelSchema] | None = None,
|
|
429
732
|
) -> str:
|
|
430
733
|
"""
|
|
431
734
|
Generates a Neo4j query from the given CartographyNodeSchema to ingest the specified nodes and relationships so that
|
|
@@ -452,19 +755,22 @@ def build_ingestion_query(
|
|
|
452
755
|
MERGE (i:$node_label{id: $dict_id_field})
|
|
453
756
|
ON CREATE SET i.firstseen = timestamp()
|
|
454
757
|
SET
|
|
758
|
+
i._module_name = "$module_name",
|
|
759
|
+
i._module_version = "$module_version",
|
|
455
760
|
$set_node_properties_statement
|
|
761
|
+
$set_ontology_node_properties_statement
|
|
456
762
|
$attach_relationships_statement
|
|
457
763
|
""",
|
|
458
764
|
)
|
|
459
765
|
|
|
460
766
|
node_props: CartographyNodeProperties = node_schema.properties
|
|
461
|
-
node_props_as_dict:
|
|
767
|
+
node_props_as_dict: dict[str, PropertyRef] = asdict(node_props)
|
|
462
768
|
|
|
463
769
|
# Handle selected relationships
|
|
464
|
-
sub_resource_rel:
|
|
770
|
+
sub_resource_rel: CartographyRelSchema | None = (
|
|
465
771
|
node_schema.sub_resource_relationship
|
|
466
772
|
)
|
|
467
|
-
other_rels:
|
|
773
|
+
other_rels: OtherRelationships | None = node_schema.other_relationships
|
|
468
774
|
if selected_relationships or selected_relationships == set():
|
|
469
775
|
sub_resource_rel, other_rels = filter_selected_relationships(
|
|
470
776
|
node_schema,
|
|
@@ -474,10 +780,16 @@ def build_ingestion_query(
|
|
|
474
780
|
ingest_query = query_template.safe_substitute(
|
|
475
781
|
node_label=node_schema.label,
|
|
476
782
|
dict_id_field=node_props.id,
|
|
783
|
+
module_name=_get_module_from_schema(node_schema),
|
|
784
|
+
module_version=_get_cartography_version(),
|
|
477
785
|
set_node_properties_statement=_build_node_properties_statement(
|
|
478
786
|
node_props_as_dict,
|
|
479
787
|
node_schema.extra_node_labels,
|
|
480
788
|
),
|
|
789
|
+
set_ontology_node_properties_statement=_build_ontology_node_properties_statement(
|
|
790
|
+
node_schema,
|
|
791
|
+
node_props_as_dict,
|
|
792
|
+
),
|
|
481
793
|
attach_relationships_statement=_build_attach_relationships_statement(
|
|
482
794
|
sub_resource_rel,
|
|
483
795
|
other_rels,
|
|
@@ -486,7 +798,7 @@ def build_ingestion_query(
|
|
|
486
798
|
return ingest_query
|
|
487
799
|
|
|
488
800
|
|
|
489
|
-
def build_create_index_queries(node_schema: CartographyNodeSchema) ->
|
|
801
|
+
def build_create_index_queries(node_schema: CartographyNodeSchema) -> list[str]:
|
|
490
802
|
"""
|
|
491
803
|
Generate queries to create indexes for the given CartographyNodeSchema and all node types attached to it via its
|
|
492
804
|
relationships.
|
|
@@ -536,7 +848,7 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
|
|
|
536
848
|
)
|
|
537
849
|
|
|
538
850
|
# Now, include extra indexes defined by the module author on the node schema's property refs.
|
|
539
|
-
node_props_as_dict:
|
|
851
|
+
node_props_as_dict: dict[str, PropertyRef] = asdict(node_schema.properties)
|
|
540
852
|
result.extend(
|
|
541
853
|
[
|
|
542
854
|
index_template.safe_substitute(
|
|
@@ -548,3 +860,192 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
|
|
|
548
860
|
],
|
|
549
861
|
)
|
|
550
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]}"
|