cartography 0.111.0rc1__py3-none-any.whl → 0.113.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.

Potentially problematic release.


This version of cartography might be problematic. Click here for more details.

Files changed (81) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +57 -0
  3. cartography/config.py +24 -0
  4. cartography/data/indexes.cypher +0 -6
  5. cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
  6. cartography/intel/aws/apigateway.py +128 -17
  7. cartography/intel/aws/apigatewayv2.py +116 -0
  8. cartography/intel/aws/ec2/instances.py +3 -1
  9. cartography/intel/aws/ec2/network_interfaces.py +1 -1
  10. cartography/intel/aws/ec2/vpc_peerings.py +262 -125
  11. cartography/intel/aws/resources.py +2 -0
  12. cartography/intel/azure/__init__.py +35 -32
  13. cartography/intel/azure/subscription.py +2 -2
  14. cartography/intel/azure/tenant.py +39 -30
  15. cartography/intel/azure/util/credentials.py +49 -174
  16. cartography/intel/entra/__init__.py +47 -1
  17. cartography/intel/entra/applications.py +220 -170
  18. cartography/intel/entra/groups.py +41 -22
  19. cartography/intel/entra/ou.py +28 -20
  20. cartography/intel/entra/users.py +24 -18
  21. cartography/intel/gcp/__init__.py +32 -11
  22. cartography/intel/gcp/compute.py +47 -12
  23. cartography/intel/gcp/dns.py +82 -169
  24. cartography/intel/gcp/iam.py +66 -54
  25. cartography/intel/gcp/storage.py +75 -159
  26. cartography/intel/github/repos.py +19 -10
  27. cartography/intel/github/util.py +12 -0
  28. cartography/intel/keycloak/__init__.py +153 -0
  29. cartography/intel/keycloak/authenticationexecutions.py +322 -0
  30. cartography/intel/keycloak/authenticationflows.py +77 -0
  31. cartography/intel/keycloak/clients.py +187 -0
  32. cartography/intel/keycloak/groups.py +126 -0
  33. cartography/intel/keycloak/identityproviders.py +94 -0
  34. cartography/intel/keycloak/organizations.py +163 -0
  35. cartography/intel/keycloak/realms.py +61 -0
  36. cartography/intel/keycloak/roles.py +202 -0
  37. cartography/intel/keycloak/scopes.py +73 -0
  38. cartography/intel/keycloak/users.py +70 -0
  39. cartography/intel/keycloak/util.py +47 -0
  40. cartography/intel/kubernetes/__init__.py +26 -0
  41. cartography/intel/kubernetes/eks.py +402 -0
  42. cartography/intel/kubernetes/rbac.py +133 -0
  43. cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
  44. cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
  45. cartography/models/aws/apigatewayv2/__init__.py +0 -0
  46. cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
  47. cartography/models/aws/ec2/vpc_peering.py +157 -0
  48. cartography/models/azure/principal.py +44 -0
  49. cartography/models/azure/tenant.py +20 -0
  50. cartography/models/gcp/dns.py +109 -0
  51. cartography/models/gcp/iam.py +3 -0
  52. cartography/models/gcp/storage/__init__.py +0 -0
  53. cartography/models/gcp/storage/bucket.py +119 -0
  54. cartography/models/keycloak/__init__.py +0 -0
  55. cartography/models/keycloak/authenticationexecution.py +160 -0
  56. cartography/models/keycloak/authenticationflow.py +54 -0
  57. cartography/models/keycloak/client.py +177 -0
  58. cartography/models/keycloak/group.py +101 -0
  59. cartography/models/keycloak/identityprovider.py +89 -0
  60. cartography/models/keycloak/organization.py +116 -0
  61. cartography/models/keycloak/organizationdomain.py +73 -0
  62. cartography/models/keycloak/realm.py +173 -0
  63. cartography/models/keycloak/role.py +126 -0
  64. cartography/models/keycloak/scope.py +73 -0
  65. cartography/models/keycloak/user.py +51 -0
  66. cartography/models/kubernetes/clusterrolebindings.py +40 -0
  67. cartography/models/kubernetes/groups.py +107 -0
  68. cartography/models/kubernetes/oidc.py +51 -0
  69. cartography/models/kubernetes/rolebindings.py +40 -0
  70. cartography/models/kubernetes/users.py +105 -0
  71. cartography/sync.py +2 -0
  72. cartography/util.py +10 -0
  73. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/METADATA +9 -5
  74. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/RECORD +78 -41
  75. cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
  76. cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
  77. cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
  78. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/WHEEL +0 -0
  79. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/entry_points.txt +0 -0
  80. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/licenses/LICENSE +0 -0
  81. {cartography-0.111.0rc1.dist-info → cartography-0.113.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,17 @@
1
1
  import logging
2
2
  from typing import Dict
3
3
  from typing import List
4
+ from typing import Tuple
4
5
 
5
6
  import neo4j
6
7
  from googleapiclient.discovery import HttpError
7
8
  from googleapiclient.discovery import Resource
8
9
 
10
+ from cartography.client.core.tx import load
11
+ from cartography.graph.job import GraphJob
9
12
  from cartography.intel.gcp import compute
10
- from cartography.util import run_cleanup_job
13
+ from cartography.models.gcp.storage.bucket import GCPBucketLabelSchema
14
+ from cartography.models.gcp.storage.bucket import GCPBucketSchema
11
15
  from cartography.util import timeit
12
16
 
13
17
  logger = logging.getLogger(__name__)
@@ -58,165 +62,85 @@ def get_gcp_buckets(storage: Resource, project_id: str) -> Dict:
58
62
 
59
63
 
60
64
  @timeit
61
- def transform_gcp_buckets(bucket_res: Dict) -> List[Dict]:
65
+ def transform_gcp_buckets_and_labels(bucket_res: Dict) -> Tuple[List[Dict], List[Dict]]:
62
66
  """
63
- Transform the GCP Storage Bucket response object for Neo4j ingestion
67
+ Transform the GCP Storage Bucket response object for Neo4j ingestion.
64
68
 
65
- :type bucket_res: The GCP storage resource object (https://cloud.google.com/storage/docs/json_api/v1/buckets)
66
- :param bucket_res: The return data
67
-
68
- :rtype: list
69
- :return: List of buckets ready for ingestion to Neo4j
69
+ :param bucket_res: The raw GCP bucket response.
70
+ :return: A tuple of (buckets, bucket_labels) ready for ingestion to Neo4j.
70
71
  """
71
72
 
72
- bucket_list = []
73
+ buckets: List[Dict] = []
74
+ labels: List[Dict] = []
73
75
  for b in bucket_res.get("items", []):
74
- bucket = {}
75
- bucket["etag"] = b.get("etag")
76
- bucket["iam_config_bucket_policy_only"] = (
77
- b.get("iamConfiguration", {})
78
- .get("bucketPolicyOnly", {})
79
- .get("enabled", None)
80
- )
81
- bucket["id"] = b["id"]
82
- bucket["labels"] = [(key, val) for (key, val) in b.get("labels", {}).items()]
83
- bucket["owner_entity"] = b.get("owner", {}).get("entity")
84
- bucket["owner_entity_id"] = b.get("owner", {}).get("entityId")
85
- bucket["kind"] = b.get("kind")
86
- bucket["location"] = b.get("location")
87
- bucket["location_type"] = b.get("locationType")
88
- bucket["meta_generation"] = b.get("metageneration", None)
89
- bucket["project_number"] = b["projectNumber"]
90
- bucket["self_link"] = b.get("selfLink")
91
- bucket["storage_class"] = b.get("storageClass")
92
- bucket["time_created"] = b.get("timeCreated")
93
- bucket["updated"] = b.get("updated")
94
- bucket["versioning_enabled"] = b.get("versioning", {}).get("enabled", None)
95
- bucket["default_event_based_hold"] = b.get("defaultEventBasedHold", None)
96
- bucket["retention_period"] = b.get("retentionPolicy", {}).get(
97
- "retentionPeriod",
98
- None,
99
- )
100
- bucket["default_kms_key_name"] = b.get("encryption", {}).get(
101
- "defaultKmsKeyName",
102
- )
103
- bucket["log_bucket"] = b.get("logging", {}).get("logBucket")
104
- bucket["requester_pays"] = b.get("billing", {}).get("requesterPays", None)
105
- bucket_list.append(bucket)
106
- return bucket_list
76
+ bucket = {
77
+ "iam_config_bucket_policy_only": (
78
+ b.get("iamConfiguration", {}).get("bucketPolicyOnly", {}).get("enabled")
79
+ ),
80
+ "id": b["id"],
81
+ # Preserve legacy bucket_id field for compatibility
82
+ "bucket_id": b["id"],
83
+ "owner_entity": b.get("owner", {}).get("entity"),
84
+ "owner_entity_id": b.get("owner", {}).get("entityId"),
85
+ "kind": b.get("kind"),
86
+ "location": b.get("location"),
87
+ "location_type": b.get("locationType"),
88
+ "meta_generation": b.get("metageneration"),
89
+ "project_number": b.get("projectNumber"),
90
+ "self_link": b.get("selfLink"),
91
+ "storage_class": b.get("storageClass"),
92
+ "time_created": b.get("timeCreated"),
93
+ "versioning_enabled": b.get("versioning", {}).get("enabled"),
94
+ "retention_period": b.get("retentionPolicy", {}).get("retentionPeriod"),
95
+ "default_kms_key_name": b.get("encryption", {}).get("defaultKmsKeyName"),
96
+ "log_bucket": b.get("logging", {}).get("logBucket"),
97
+ "requester_pays": b.get("billing", {}).get("requesterPays"),
98
+ }
99
+ buckets.append(bucket)
100
+ for key, val in b.get("labels", {}).items():
101
+ labels.append(
102
+ {
103
+ "id": f"GCPBucket_{key}",
104
+ "key": key,
105
+ "value": val,
106
+ "bucket_id": b["id"],
107
+ }
108
+ )
109
+ return buckets, labels
107
110
 
108
111
 
109
112
  @timeit
110
113
  def load_gcp_buckets(
111
114
  neo4j_session: neo4j.Session,
112
115
  buckets: List[Dict],
116
+ project_id: str,
113
117
  gcp_update_tag: int,
114
118
  ) -> None:
115
- """
116
- Ingest GCP Storage Buckets to Neo4j
117
-
118
- :type neo4j_session: Neo4j session object
119
- :param neo4j session: The Neo4j session object
120
-
121
- :type buckets: list
122
- :param buckets: List of GCP Storage Buckets to injest
123
-
124
- :type gcp_update_tag: timestamp
125
- :param gcp_update_tag: The timestamp value to set our new Neo4j nodes with
126
-
127
- :rtype: NoneType
128
- :return: Nothing
129
- """
130
-
131
- query = """
132
- MERGE(p:GCPProject{projectnumber:$ProjectNumber})
133
- ON CREATE SET p.firstseen = timestamp()
134
- SET p.lastupdated = $gcp_update_tag
135
-
136
- MERGE(bucket:GCPBucket{id:$BucketId})
137
- ON CREATE SET bucket.firstseen = timestamp(),
138
- bucket.bucket_id = $BucketId
139
- SET bucket.self_link = $SelfLink,
140
- bucket.project_number = $ProjectNumber,
141
- bucket.kind = $Kind,
142
- bucket.location = $Location,
143
- bucket.location_type = $LocationType,
144
- bucket.meta_generation = $MetaGeneration,
145
- bucket.storage_class = $StorageClass,
146
- bucket.time_created = $TimeCreated,
147
- bucket.retention_period = $RetentionPeriod,
148
- bucket.iam_config_bucket_policy_only = $IamConfigBucketPolicyOnly,
149
- bucket.owner_entity = $OwnerEntity,
150
- bucket.owner_entity_id = $OwnerEntityId,
151
- bucket.lastupdated = $gcp_update_tag,
152
- bucket.versioning_enabled = $VersioningEnabled,
153
- bucket.log_bucket = $LogBucket,
154
- bucket.requester_pays = $RequesterPays,
155
- bucket.default_kms_key_name = $DefaultKmsKeyName
156
-
157
- MERGE (p)-[r:RESOURCE]->(bucket)
158
- ON CREATE SET r.firstseen = timestamp()
159
- SET r.lastupdated = $gcp_update_tag
160
- """
161
- for bucket in buckets:
162
- neo4j_session.run(
163
- query,
164
- ProjectNumber=bucket["project_number"],
165
- BucketId=bucket["id"],
166
- SelfLink=bucket["self_link"],
167
- Kind=bucket["kind"],
168
- Location=bucket["location"],
169
- LocationType=bucket["location_type"],
170
- MetaGeneration=bucket["meta_generation"],
171
- StorageClass=bucket["storage_class"],
172
- TimeCreated=bucket["time_created"],
173
- RetentionPeriod=bucket["retention_period"],
174
- IamConfigBucketPolicyOnly=bucket["iam_config_bucket_policy_only"],
175
- OwnerEntity=bucket["owner_entity"],
176
- OwnerEntityId=bucket["owner_entity_id"],
177
- VersioningEnabled=bucket["versioning_enabled"],
178
- LogBucket=bucket["log_bucket"],
179
- RequesterPays=bucket["requester_pays"],
180
- DefaultKmsKeyName=bucket["default_kms_key_name"],
181
- gcp_update_tag=gcp_update_tag,
182
- )
183
- _attach_gcp_bucket_labels(neo4j_session, bucket, gcp_update_tag)
119
+ """Ingest GCP Storage Buckets to Neo4j."""
120
+ load(
121
+ neo4j_session,
122
+ GCPBucketSchema(),
123
+ buckets,
124
+ lastupdated=gcp_update_tag,
125
+ PROJECT_ID=project_id,
126
+ )
184
127
 
185
128
 
186
129
  @timeit
187
- def _attach_gcp_bucket_labels(
130
+ def load_gcp_bucket_labels(
188
131
  neo4j_session: neo4j.Session,
189
- bucket: Resource,
132
+ bucket_labels: List[Dict],
133
+ project_id: str,
190
134
  gcp_update_tag: int,
191
135
  ) -> None:
192
- """
193
- Attach GCP bucket labels to the bucket.
194
- :param neo4j_session: The neo4j session
195
- :param bucket: The GCP bucket object
196
- :param gcp_update_tag: The update tag for this sync
197
- :return: Nothing
198
- """
199
- query = """
200
- MERGE (l:Label:GCPBucketLabel{id: $BucketLabelId})
201
- ON CREATE SET l.firstseen = timestamp(),
202
- l.key = $Key
203
- SET l.value = $Value,
204
- l.lastupdated = $gcp_update_tag
205
- WITH l
206
- MATCH (bucket:GCPBucket{id:$BucketId})
207
- MERGE (l)<-[r:LABELED]-(bucket)
208
- ON CREATE SET r.firstseen = timestamp()
209
- SET r.lastupdated = $gcp_update_tag
210
- """
211
- for key, val in bucket.get("labels", []):
212
- neo4j_session.run(
213
- query,
214
- BucketLabelId=f"GCPBucket_{key}",
215
- Key=key,
216
- Value=val,
217
- BucketId=bucket["id"],
218
- gcp_update_tag=gcp_update_tag,
219
- )
136
+ """Ingest GCP Storage Bucket labels and attach them to buckets."""
137
+ load(
138
+ neo4j_session,
139
+ GCPBucketLabelSchema(),
140
+ bucket_labels,
141
+ lastupdated=gcp_update_tag,
142
+ PROJECT_ID=project_id,
143
+ )
220
144
 
221
145
 
222
146
  @timeit
@@ -224,22 +148,14 @@ def cleanup_gcp_buckets(
224
148
  neo4j_session: neo4j.Session,
225
149
  common_job_parameters: Dict,
226
150
  ) -> None:
227
- """
228
- Delete out-of-date GCP Storage Bucket nodes and relationships
229
-
230
- :type neo4j_session: The Neo4j session object
231
- :param neo4j_session: The Neo4j session
232
-
233
- :type common_job_parameters: dict
234
- :param common_job_parameters: Dictionary of other job parameters to pass to Neo4j
235
-
236
- :rtype: NoneType
237
- :return: Nothing
238
- """
239
- run_cleanup_job(
240
- "gcp_storage_bucket_cleanup.json",
151
+ """Delete out-of-date GCP Storage Bucket nodes and relationships."""
152
+ # Bucket labels depend on buckets, so we must remove labels first to avoid
153
+ # dangling references before deleting the buckets themselves.
154
+ GraphJob.from_node_schema(GCPBucketLabelSchema(), common_job_parameters).run(
155
+ neo4j_session,
156
+ )
157
+ GraphJob.from_node_schema(GCPBucketSchema(), common_job_parameters).run(
241
158
  neo4j_session,
242
- common_job_parameters,
243
159
  )
244
160
 
245
161
 
@@ -274,7 +190,7 @@ def sync_gcp_buckets(
274
190
  """
275
191
  logger.info("Syncing Storage objects for project %s.", project_id)
276
192
  storage_res = get_gcp_buckets(storage, project_id)
277
- bucket_list = transform_gcp_buckets(storage_res)
278
- load_gcp_buckets(neo4j_session, bucket_list, gcp_update_tag)
279
- # TODO scope the cleanup to the current project - https://github.com/cartography-cncf/cartography/issues/381
193
+ buckets, bucket_labels = transform_gcp_buckets_and_labels(storage_res)
194
+ load_gcp_buckets(neo4j_session, buckets, project_id, gcp_update_tag)
195
+ load_gcp_bucket_labels(neo4j_session, bucket_labels, project_id, gcp_update_tag)
280
196
  cleanup_gcp_buckets(neo4j_session, common_job_parameters)
@@ -41,12 +41,12 @@ UserAffiliationAndRepoPermission = namedtuple(
41
41
 
42
42
 
43
43
  GITHUB_ORG_REPOS_PAGINATED_GRAPHQL = """
44
- query($login: String!, $cursor: String) {
44
+ query($login: String!, $cursor: String, $count: Int!) {
45
45
  organization(login: $login)
46
46
  {
47
47
  url
48
48
  login
49
- repositories(first: 50, after: $cursor){
49
+ repositories(first: $count, after: $cursor){
50
50
  pageInfo{
51
51
  endCursor
52
52
  hasNextPage
@@ -168,14 +168,22 @@ def _get_repo_collaborators_inner_func(
168
168
  repo_name = repo["name"]
169
169
  repo_url = repo["url"]
170
170
 
171
- if (
172
- affiliation == "OUTSIDE" and repo["outsideCollaborators"]["totalCount"] == 0
173
- ) or (
174
- affiliation == "DIRECT" and repo["directCollaborators"]["totalCount"] == 0
175
- ):
176
- # repo has no collabs of the affiliation type we're looking for, so don't waste time making an API call
177
- result[repo_url] = []
178
- continue
171
+ # Guard against None when collaborator fields are not accessible due to permissions.
172
+ direct_info = repo.get("directCollaborators")
173
+ outside_info = repo.get("outsideCollaborators")
174
+
175
+ if affiliation == "OUTSIDE":
176
+ total_outside = 0 if not outside_info else outside_info.get("totalCount", 0)
177
+ if total_outside == 0:
178
+ # No outside collaborators or not permitted to view; skip API calls for this repo.
179
+ result[repo_url] = []
180
+ continue
181
+ else: # DIRECT
182
+ total_direct = 0 if not direct_info else direct_info.get("totalCount", 0)
183
+ if total_direct == 0:
184
+ # No direct collaborators or not permitted to view; skip API calls for this repo.
185
+ result[repo_url] = []
186
+ continue
179
187
 
180
188
  logger.info(f"Loading {affiliation} collaborators for repo {repo_name}.")
181
189
  collaborators = _get_repo_collaborators(
@@ -290,6 +298,7 @@ def get(token: str, api_url: str, organization: str) -> List[Dict]:
290
298
  organization,
291
299
  GITHUB_ORG_REPOS_PAGINATED_GRAPHQL,
292
300
  "repositories",
301
+ count=50,
293
302
  )
294
303
  return repos.nodes
295
304
 
@@ -157,6 +157,18 @@ def fetch_all(
157
157
  retry += 1
158
158
  exc = err
159
159
  except requests.exceptions.HTTPError as err:
160
+ if (
161
+ err.response is not None
162
+ and err.response.status_code == 502
163
+ and kwargs.get("count")
164
+ and kwargs["count"] > 1
165
+ ):
166
+ kwargs["count"] = max(1, kwargs["count"] // 2)
167
+ logger.warning(
168
+ "GitHub: Received 502 response. Reducing page size to %s and retrying.",
169
+ kwargs["count"],
170
+ )
171
+ continue
160
172
  retry += 1
161
173
  exc = err
162
174
  except requests.exceptions.ChunkedEncodingError as err:
@@ -0,0 +1,153 @@
1
+ import logging
2
+
3
+ import neo4j
4
+ import requests
5
+
6
+ import cartography.intel.keycloak.authenticationexecutions
7
+ import cartography.intel.keycloak.authenticationflows
8
+ import cartography.intel.keycloak.clients
9
+ import cartography.intel.keycloak.groups
10
+ import cartography.intel.keycloak.identityproviders
11
+ import cartography.intel.keycloak.organizations
12
+ import cartography.intel.keycloak.realms
13
+ import cartography.intel.keycloak.roles
14
+ import cartography.intel.keycloak.scopes
15
+ import cartography.intel.keycloak.users
16
+ from cartography.config import Config
17
+ from cartography.util import timeit
18
+
19
+ logger = logging.getLogger(__name__)
20
+ _TIMEOUT = (60, 60)
21
+
22
+
23
+ @timeit
24
+ def start_keycloak_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
25
+ """
26
+ If this module is configured, perform ingestion of Keycloak data. Otherwise warn and exit
27
+ :param neo4j_session: Neo4J session for database interface
28
+ :param config: A cartography.config object
29
+ :return: None
30
+ """
31
+ if (
32
+ not config.keycloak_client_id
33
+ or not config.keycloak_client_secret
34
+ or not config.keycloak_url
35
+ or not config.keycloak_realm
36
+ ):
37
+ logger.info(
38
+ "Keycloak import is not configured - skipping this module. "
39
+ "See docs to configure.",
40
+ )
41
+ return
42
+
43
+ # Create requests sessions
44
+ with requests.session() as api_session:
45
+ payload = {
46
+ "grant_type": "client_credentials",
47
+ "client_id": config.keycloak_client_id,
48
+ "client_secret": config.keycloak_client_secret,
49
+ }
50
+ req = api_session.post(
51
+ f"{config.keycloak_url}/realms/{config.keycloak_realm}/protocol/openid-connect/token",
52
+ data=payload,
53
+ timeout=_TIMEOUT,
54
+ )
55
+ req.raise_for_status()
56
+ api_session.headers.update(
57
+ {"Authorization": f'Bearer {req.json()["access_token"]}'}
58
+ )
59
+
60
+ common_job_parameters = {
61
+ "UPDATE_TAG": config.update_tag,
62
+ }
63
+
64
+ for realm in cartography.intel.keycloak.realms.sync(
65
+ neo4j_session, api_session, config.keycloak_url, common_job_parameters
66
+ ):
67
+ realm_scopped_job_parameters = {
68
+ "UPDATE_TAG": config.update_tag,
69
+ "REALM": realm["realm"],
70
+ "REALM_ID": realm["id"],
71
+ }
72
+ cartography.intel.keycloak.users.sync(
73
+ neo4j_session,
74
+ api_session,
75
+ config.keycloak_url,
76
+ realm_scopped_job_parameters,
77
+ )
78
+ cartography.intel.keycloak.identityproviders.sync(
79
+ neo4j_session,
80
+ api_session,
81
+ config.keycloak_url,
82
+ realm_scopped_job_parameters,
83
+ )
84
+ scopes = cartography.intel.keycloak.scopes.sync(
85
+ neo4j_session,
86
+ api_session,
87
+ config.keycloak_url,
88
+ realm_scopped_job_parameters,
89
+ )
90
+ scope_ids = [s["id"] for s in scopes]
91
+ flows = cartography.intel.keycloak.authenticationflows.sync(
92
+ neo4j_session,
93
+ api_session,
94
+ config.keycloak_url,
95
+ realm_scopped_job_parameters,
96
+ )
97
+ flow_aliases_to_id = {f["alias"]: f["id"] for f in flows}
98
+ cartography.intel.keycloak.authenticationexecutions.sync(
99
+ neo4j_session,
100
+ api_session,
101
+ config.keycloak_url,
102
+ realm_scopped_job_parameters,
103
+ list(flow_aliases_to_id.keys()),
104
+ )
105
+ realm_default_flows = {
106
+ "browser": flow_aliases_to_id.get(realm.get("browserFlow")),
107
+ "registration": flow_aliases_to_id.get(realm.get("registrationFlow")),
108
+ "direct_grant": flow_aliases_to_id.get(realm.get("directGrantFlow")),
109
+ "reset_credentials": flow_aliases_to_id.get(
110
+ realm.get("resetCredentialsFlow")
111
+ ),
112
+ "client_authentication": flow_aliases_to_id.get(
113
+ realm.get("clientAuthenticationFlow")
114
+ ),
115
+ "docker_authentication": flow_aliases_to_id.get(
116
+ realm.get("dockerAuthenticationFlow")
117
+ ),
118
+ "first_broker_login": flow_aliases_to_id.get(
119
+ realm.get("firstBrokerLoginFlow")
120
+ ),
121
+ }
122
+
123
+ clients = cartography.intel.keycloak.clients.sync(
124
+ neo4j_session,
125
+ api_session,
126
+ config.keycloak_url,
127
+ realm_scopped_job_parameters,
128
+ realm_default_flows,
129
+ )
130
+ client_ids = [c["id"] for c in clients]
131
+ cartography.intel.keycloak.roles.sync(
132
+ neo4j_session,
133
+ api_session,
134
+ config.keycloak_url,
135
+ realm_scopped_job_parameters,
136
+ client_ids,
137
+ scope_ids,
138
+ )
139
+ cartography.intel.keycloak.groups.sync(
140
+ neo4j_session,
141
+ api_session,
142
+ config.keycloak_url,
143
+ realm_scopped_job_parameters,
144
+ )
145
+
146
+ # Organizations if they are enabled
147
+ if realm.get("organizationsEnabled", False):
148
+ cartography.intel.keycloak.organizations.sync(
149
+ neo4j_session,
150
+ api_session,
151
+ config.keycloak_url,
152
+ realm_scopped_job_parameters,
153
+ )