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
@@ -0,0 +1,94 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import requests
6
+
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.intel.keycloak.util import get_paginated
10
+ from cartography.models.keycloak.identityprovider import KeycloakIdentityProviderSchema
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
15
+ _TIMEOUT = (60, 60)
16
+
17
+
18
+ @timeit
19
+ def sync(
20
+ neo4j_session: neo4j.Session,
21
+ api_session: requests.Session,
22
+ base_url: str,
23
+ common_job_parameters: dict[str, Any],
24
+ ) -> None:
25
+ identityproviders = get(
26
+ api_session,
27
+ base_url,
28
+ common_job_parameters["REALM"],
29
+ )
30
+ idps_transformed = transform(identityproviders)
31
+ load_identityproviders(
32
+ neo4j_session,
33
+ idps_transformed,
34
+ common_job_parameters["REALM"],
35
+ common_job_parameters["UPDATE_TAG"],
36
+ )
37
+ cleanup(neo4j_session, common_job_parameters)
38
+
39
+
40
+ @timeit
41
+ def get(
42
+ api_session: requests.Session,
43
+ base_url: str,
44
+ realm: str,
45
+ ) -> list[dict[str, Any]]:
46
+ result: list[dict[str, Any]] = []
47
+ url = f"{base_url}/admin/realms/{realm}/identity-provider/instances"
48
+ for idp in get_paginated(api_session, url, params={"briefRepresentation": False}):
49
+ # Get members
50
+ members_url = f"{base_url}/admin/realms/{realm}/users"
51
+ idp["_members"] = list(
52
+ get_paginated(
53
+ api_session,
54
+ members_url,
55
+ params={"idpAlias": idp["alias"], "briefRepresentation": True},
56
+ )
57
+ )
58
+ result.append(idp)
59
+ return result
60
+
61
+
62
+ def transform(idps: list[dict[str, Any]]) -> list[dict[str, Any]]:
63
+ for idp in idps:
64
+ idp["_member_ids"] = [member["id"] for member in idp["_members"]]
65
+ idp.pop("_members", None)
66
+ return idps
67
+
68
+
69
+ @timeit
70
+ def load_identityproviders(
71
+ neo4j_session: neo4j.Session,
72
+ data: list[dict[str, Any]],
73
+ realm: str,
74
+ update_tag: int,
75
+ ) -> None:
76
+ logger.info(
77
+ "Loading %d Keycloak IdentityProviders (%s) into Neo4j.", len(data), realm
78
+ )
79
+ load(
80
+ neo4j_session,
81
+ KeycloakIdentityProviderSchema(),
82
+ data,
83
+ LASTUPDATED=update_tag,
84
+ REALM=realm,
85
+ )
86
+
87
+
88
+ @timeit
89
+ def cleanup(
90
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
91
+ ) -> None:
92
+ GraphJob.from_node_schema(
93
+ KeycloakIdentityProviderSchema(), common_job_parameters
94
+ ).run(neo4j_session)
@@ -0,0 +1,163 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Tuple
4
+
5
+ import neo4j
6
+ import requests
7
+
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.intel.keycloak.util import get_paginated
11
+ from cartography.models.keycloak.organization import KeycloakOrganizationSchema
12
+ from cartography.models.keycloak.organizationdomain import (
13
+ KeycloakOrganizationDomainSchema,
14
+ )
15
+ from cartography.util import timeit
16
+
17
+ logger = logging.getLogger(__name__)
18
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
19
+ _TIMEOUT = (60, 60)
20
+
21
+
22
+ @timeit
23
+ def sync(
24
+ neo4j_session: neo4j.Session,
25
+ api_session: requests.Session,
26
+ base_url: str,
27
+ common_job_parameters: dict[str, Any],
28
+ ) -> None:
29
+ organizations = get(
30
+ api_session,
31
+ base_url,
32
+ common_job_parameters["REALM"],
33
+ )
34
+ transformed_orgs, transformed_domains = transform(organizations)
35
+ load_organizations(
36
+ neo4j_session,
37
+ transformed_orgs,
38
+ common_job_parameters["REALM"],
39
+ common_job_parameters["UPDATE_TAG"],
40
+ )
41
+ load_org_domains(
42
+ neo4j_session,
43
+ transformed_domains,
44
+ common_job_parameters["REALM"],
45
+ common_job_parameters["UPDATE_TAG"],
46
+ )
47
+ cleanup(neo4j_session, common_job_parameters)
48
+
49
+
50
+ def transform(
51
+ organizations: list[dict[str, Any]],
52
+ ) -> Tuple[list[dict[str, Any]], list[dict[str, Any]]]:
53
+ transformed_orgs = []
54
+ transformed_domains = {}
55
+ for org in organizations:
56
+ # Transform members to a list of IDs
57
+ org["_managed_members"] = []
58
+ org["_unmanaged_members"] = []
59
+ for member in org.get("_members", []):
60
+ if member.get("membershipType") == "UNMANAGED":
61
+ org["_unmanaged_members"].append(member["id"])
62
+ else:
63
+ org["_managed_members"].append(member["id"])
64
+ org.pop("_members", None)
65
+ # Transform identity providers to a list of IDs
66
+ org["_idp_ids"] = [
67
+ idp["internalId"] for idp in org.get("_identity_providers", [])
68
+ ]
69
+ org.pop("_identity_providers", None)
70
+ # Extract domains
71
+ domains = org.get("domains", [])
72
+ for domain in domains:
73
+ domain_id = f"{org['id']}-{domain['name']}"
74
+ transformed_domains[domain_id] = {
75
+ "id": domain_id,
76
+ "verified": domain.get("verified", False),
77
+ "name": domain["name"],
78
+ "organization_id": org["id"],
79
+ }
80
+ org.pop("domains", None)
81
+ transformed_orgs.append(org)
82
+ return transformed_orgs, list(transformed_domains.values())
83
+
84
+
85
+ @timeit
86
+ def get(
87
+ api_session: requests.Session,
88
+ base_url: str,
89
+ realm: str,
90
+ ) -> list[dict[str, Any]]:
91
+ result: list[dict[str, Any]] = []
92
+ url = f"{base_url}/admin/realms/{realm}/organizations"
93
+ for org in get_paginated(api_session, url):
94
+ # Get members
95
+ members_url = (
96
+ f"{base_url}/admin/realms/{realm}/organizations/{org['id']}/members"
97
+ )
98
+ org["_members"] = list(
99
+ get_paginated(
100
+ api_session,
101
+ members_url,
102
+ params={"briefRepresentation": True},
103
+ )
104
+ )
105
+ # Get Identity Providers
106
+ idp_url = f"{base_url}/admin/realms/{realm}/organizations/{org['id']}/identity-providers"
107
+ org["_identity_providers"] = list(
108
+ get_paginated(
109
+ api_session,
110
+ idp_url,
111
+ params={"briefRepresentation": True},
112
+ )
113
+ )
114
+ result.append(org)
115
+ return result
116
+
117
+
118
+ @timeit
119
+ def load_organizations(
120
+ neo4j_session: neo4j.Session,
121
+ data: list[dict[str, Any]],
122
+ realm: str,
123
+ update_tag: int,
124
+ ) -> None:
125
+ logger.info("Loading %d Keycloak Organizations (%s) into Neo4j.", len(data), realm)
126
+ load(
127
+ neo4j_session,
128
+ KeycloakOrganizationSchema(),
129
+ data,
130
+ LASTUPDATED=update_tag,
131
+ REALM=realm,
132
+ )
133
+
134
+
135
+ @timeit
136
+ def load_org_domains(
137
+ neo4j_session: neo4j.Session,
138
+ data: list[dict[str, Any]],
139
+ realm: str,
140
+ update_tag: int,
141
+ ) -> None:
142
+ logger.info(
143
+ "Loading %d Keycloak Organization Domains (%s) into Neo4j.", len(data), realm
144
+ )
145
+ load(
146
+ neo4j_session,
147
+ KeycloakOrganizationDomainSchema(),
148
+ data,
149
+ LASTUPDATED=update_tag,
150
+ REALM=realm,
151
+ )
152
+
153
+
154
+ @timeit
155
+ def cleanup(
156
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
157
+ ) -> None:
158
+ GraphJob.from_node_schema(
159
+ KeycloakOrganizationDomainSchema(), common_job_parameters
160
+ ).run(neo4j_session)
161
+ GraphJob.from_node_schema(KeycloakOrganizationSchema(), common_job_parameters).run(
162
+ neo4j_session
163
+ )
@@ -0,0 +1,61 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import requests
6
+
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.models.keycloak.realm import KeycloakRealmSchema
10
+ from cartography.util import timeit
11
+
12
+ logger = logging.getLogger(__name__)
13
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
14
+ _TIMEOUT = (60, 60)
15
+
16
+
17
+ @timeit
18
+ def sync(
19
+ neo4j_session: neo4j.Session,
20
+ api_session: requests.Session,
21
+ base_url: str,
22
+ common_job_parameters: dict[str, Any],
23
+ ) -> list[dict]:
24
+ realms = get(api_session, base_url)
25
+ load_realms(neo4j_session, realms, common_job_parameters["UPDATE_TAG"])
26
+ cleanup(neo4j_session, common_job_parameters)
27
+ return realms
28
+
29
+
30
+ @timeit
31
+ def get(
32
+ api_session: requests.Session,
33
+ base_url: str,
34
+ ) -> list[dict[str, Any]]:
35
+ req = api_session.get(f"{base_url}/admin/realms", timeout=_TIMEOUT)
36
+ req.raise_for_status()
37
+ return req.json()
38
+
39
+
40
+ @timeit
41
+ def load_realms(
42
+ neo4j_session: neo4j.Session,
43
+ data: list[dict[str, Any]],
44
+ update_tag: int,
45
+ ) -> None:
46
+ logger.info("Loading %d Keycloak Realms into Neo4j.", len(data))
47
+ load(
48
+ neo4j_session,
49
+ KeycloakRealmSchema(),
50
+ data,
51
+ LASTUPDATED=update_tag,
52
+ )
53
+
54
+
55
+ @timeit
56
+ def cleanup(
57
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
58
+ ) -> None:
59
+ GraphJob.from_node_schema(KeycloakRealmSchema(), common_job_parameters).run(
60
+ neo4j_session
61
+ )
@@ -0,0 +1,202 @@
1
+ import logging
2
+ from typing import Any
3
+ from urllib.parse import quote
4
+
5
+ import neo4j
6
+ import requests
7
+
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.intel.keycloak.util import get_paginated
11
+ from cartography.models.keycloak.role import KeycloakRoleSchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
16
+ _TIMEOUT = (60, 60)
17
+
18
+
19
+ @timeit
20
+ def sync(
21
+ neo4j_session: neo4j.Session,
22
+ api_session: requests.Session,
23
+ base_url: str,
24
+ common_job_parameters: dict[str, Any],
25
+ client_ids: list[str],
26
+ scope_ids: list[str],
27
+ ) -> None:
28
+ roles = get(api_session, base_url, common_job_parameters["REALM"], client_ids)
29
+ scope_role_mapping = get_mapping(
30
+ api_session,
31
+ base_url,
32
+ common_job_parameters["REALM"],
33
+ scope_ids,
34
+ )
35
+ transformed_roles = transform(roles, scope_role_mapping)
36
+ load_roles(
37
+ neo4j_session,
38
+ transformed_roles,
39
+ common_job_parameters["REALM"],
40
+ common_job_parameters["UPDATE_TAG"],
41
+ )
42
+ cleanup(neo4j_session, common_job_parameters)
43
+
44
+
45
+ @timeit
46
+ def get(
47
+ api_session: requests.Session, base_url: str, realm: str, client_ids: list[str]
48
+ ) -> list[dict[str, Any]]:
49
+ roles_by_id: dict[str, dict[str, Any]] = {}
50
+
51
+ # Get roles at the REALM level
52
+ url = f"{base_url}/admin/realms/{realm}/roles"
53
+ for role in get_paginated(api_session, url, params={"briefRepresentation": False}):
54
+ if role.get("composite", False):
55
+ # If the role is composite, we need to get its composites
56
+ composite_roles = get_paginated(
57
+ api_session,
58
+ f"{base_url}/admin/realms/{realm}/roles-by-id/{role['id']}/composites",
59
+ )
60
+ role["_composite_roles"] = [
61
+ composite_role["id"] for composite_role in composite_roles
62
+ ]
63
+ # Get the direct members
64
+ direct_members = get_paginated(
65
+ api_session,
66
+ f"{base_url}/admin/realms/{realm}/roles/{quote(role['name'])}/users",
67
+ )
68
+ role["_direct_members"] = [member["id"] for member in direct_members]
69
+ roles_by_id[role["id"]] = role
70
+
71
+ # Get roles for each client
72
+ for client_id in client_ids:
73
+ url = f"{base_url}/admin/realms/{realm}/clients/{client_id}/roles"
74
+ for role in get_paginated(
75
+ api_session, url, params={"briefRepresentation": False}
76
+ ):
77
+ # If the role is composite, we need to get its composites
78
+ if role.get("composite", False):
79
+ composite_roles = get_paginated(
80
+ api_session,
81
+ f"{base_url}/admin/realms/{realm}/roles-by-id/{role['id']}/composites",
82
+ )
83
+ role["_composite_roles"] = [
84
+ composite_role["id"] for composite_role in composite_roles
85
+ ]
86
+ # Get the direct members
87
+ direct_members = get_paginated(
88
+ api_session,
89
+ f"{base_url}/admin/realms/{realm}/clients/{client_id}/roles/{quote(role['name'])}/users",
90
+ )
91
+ role["_direct_members"] = [member["id"] for member in direct_members]
92
+ roles_by_id[role["id"]] = role
93
+ return list(roles_by_id.values())
94
+
95
+
96
+ @timeit
97
+ def get_mapping(
98
+ api_session: requests.Session,
99
+ base_url: str,
100
+ realm: str,
101
+ scope_ids: list[str],
102
+ ) -> dict[str, Any]:
103
+ result: dict[str, Any] = {}
104
+ for scope_id in scope_ids:
105
+ mappings_url = (
106
+ f"{base_url}/admin/realms/{realm}/client-scopes/{scope_id}/scope-mappings"
107
+ )
108
+ req = api_session.get(
109
+ mappings_url,
110
+ timeout=_TIMEOUT,
111
+ )
112
+ req.raise_for_status()
113
+ result[scope_id] = req.json()
114
+ return result
115
+
116
+
117
+ def transform(
118
+ roles: list[dict[str, Any]], scope_role_mapping: dict[str, Any]
119
+ ) -> list[dict[str, Any]]:
120
+ transformed_roles = []
121
+
122
+ # Transform the mapping of scopes to roles
123
+ scopes_by_roles: dict[str, list[str]] = {}
124
+ for scope_id, mapping in scope_role_mapping.items():
125
+ for client_details in mapping.get("clientMappings", {}).values():
126
+ for client_mapping in client_details.get("mappings", []):
127
+ scopes_by_roles.setdefault(client_mapping["id"], []).append(scope_id)
128
+ for realm_mapping in mapping.get("realmMappings", []):
129
+ scopes_by_roles.setdefault(realm_mapping["id"], []).append(scope_id)
130
+
131
+ for role in roles:
132
+ role["_scope_ids"] = scopes_by_roles.get(role["id"], None)
133
+ transformed_roles.append(role)
134
+
135
+ return _order_dicts_dfs(
136
+ transformed_roles, children_key="_composite_roles", ignore_unknown_children=True
137
+ )
138
+
139
+
140
+ @timeit
141
+ def load_roles(
142
+ neo4j_session: neo4j.Session,
143
+ data: list[dict[str, Any]],
144
+ realm: str,
145
+ update_tag: int,
146
+ ) -> None:
147
+ logger.info("Loading %d Keycloak Roles (%s) into Neo4j.", len(data), realm)
148
+ load(
149
+ neo4j_session,
150
+ KeycloakRoleSchema(),
151
+ data,
152
+ LASTUPDATED=update_tag,
153
+ REALM=realm,
154
+ )
155
+
156
+
157
+ @timeit
158
+ def cleanup(
159
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
160
+ ) -> None:
161
+ GraphJob.from_node_schema(KeycloakRoleSchema(), common_job_parameters).run(
162
+ neo4j_session
163
+ )
164
+
165
+
166
+ def _order_dicts_dfs(
167
+ nodes: list[dict[str, Any]],
168
+ children_key: str = "children",
169
+ ignore_unknown_children: bool = False,
170
+ ) -> list[dict[str, Any]]:
171
+ by_id = {n["id"]: n for n in nodes}
172
+
173
+ WHITE, GRAY, BLACK = 0, 1, 2 # unvisited, in stack, done
174
+ state: dict[str, int] = {}
175
+ ordered: list[dict[str, Any]] = []
176
+
177
+ def visit(node_id: str, path: list[str]) -> None:
178
+ s = state.get(node_id, WHITE)
179
+ if s == GRAY:
180
+ cycle = " -> ".join(path + [node_id])
181
+ raise ValueError(f"Cycle detected: {cycle}")
182
+ if s == BLACK:
183
+ return
184
+
185
+ state[node_id] = GRAY
186
+ node = by_id[node_id]
187
+
188
+ for child_id in node.get(children_key, []):
189
+ if child_id not in by_id:
190
+ if ignore_unknown_children:
191
+ continue
192
+ raise KeyError(f"Unknown child id: {child_id}")
193
+ visit(child_id, path + [node_id])
194
+
195
+ state[node_id] = BLACK
196
+ ordered.append(node) # post-order: children before parent
197
+
198
+ for nid in by_id:
199
+ if state.get(nid, WHITE) == WHITE:
200
+ visit(nid, [])
201
+
202
+ return ordered
@@ -0,0 +1,73 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import requests
6
+
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.intel.keycloak.util import get_paginated
10
+ from cartography.models.keycloak.scope import KeycloakScopeSchema
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
15
+ _TIMEOUT = (60, 60)
16
+
17
+
18
+ @timeit
19
+ def sync(
20
+ neo4j_session: neo4j.Session,
21
+ api_session: requests.Session,
22
+ base_url: str,
23
+ common_job_parameters: dict[str, Any],
24
+ ) -> list[dict[str, Any]]:
25
+ scopes = get(
26
+ api_session,
27
+ base_url,
28
+ common_job_parameters["REALM"],
29
+ )
30
+ load_scopes(
31
+ neo4j_session,
32
+ scopes,
33
+ common_job_parameters["REALM"],
34
+ common_job_parameters["UPDATE_TAG"],
35
+ )
36
+ cleanup(neo4j_session, common_job_parameters)
37
+ return scopes
38
+
39
+
40
+ @timeit
41
+ def get(
42
+ api_session: requests.Session,
43
+ base_url: str,
44
+ realm: str,
45
+ ) -> list[dict[str, Any]]:
46
+ url = f"{base_url}/admin/realms/{realm}/client-scopes"
47
+ return list(get_paginated(api_session, url, params={"briefRepresentation": False}))
48
+
49
+
50
+ @timeit
51
+ def load_scopes(
52
+ neo4j_session: neo4j.Session,
53
+ data: list[dict[str, Any]],
54
+ realm: str,
55
+ update_tag: int,
56
+ ) -> None:
57
+ logger.info("Loading %d Keycloak Scopes (%s) into Neo4j.", len(data), realm)
58
+ load(
59
+ neo4j_session,
60
+ KeycloakScopeSchema(),
61
+ data,
62
+ LASTUPDATED=update_tag,
63
+ REALM=realm,
64
+ )
65
+
66
+
67
+ @timeit
68
+ def cleanup(
69
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
70
+ ) -> None:
71
+ GraphJob.from_node_schema(KeycloakScopeSchema(), common_job_parameters).run(
72
+ neo4j_session
73
+ )
@@ -0,0 +1,70 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import requests
6
+
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.intel.keycloak.util import get_paginated
10
+ from cartography.models.keycloak.user import KeycloakUserSchema
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @timeit
17
+ def sync(
18
+ neo4j_session: neo4j.Session,
19
+ api_session: requests.Session,
20
+ base_url: str,
21
+ common_job_parameters: dict[str, Any],
22
+ ) -> None:
23
+ users = get(
24
+ api_session,
25
+ base_url,
26
+ common_job_parameters["REALM"],
27
+ )
28
+ load_users(
29
+ neo4j_session,
30
+ users,
31
+ common_job_parameters["REALM"],
32
+ common_job_parameters["UPDATE_TAG"],
33
+ )
34
+ cleanup(neo4j_session, common_job_parameters)
35
+
36
+
37
+ @timeit
38
+ def get(
39
+ api_session: requests.Session,
40
+ base_url: str,
41
+ realm: str,
42
+ ) -> list[dict[str, Any]]:
43
+ url = f"{base_url}/admin/realms/{realm}/users"
44
+ return list(get_paginated(api_session, url))
45
+
46
+
47
+ @timeit
48
+ def load_users(
49
+ neo4j_session: neo4j.Session,
50
+ data: list[dict[str, Any]],
51
+ realm: str,
52
+ update_tag: int,
53
+ ) -> None:
54
+ logger.info("Loading %d Keycloak Users (%s) into Neo4j.", len(data), realm)
55
+ load(
56
+ neo4j_session,
57
+ KeycloakUserSchema(),
58
+ data,
59
+ LASTUPDATED=update_tag,
60
+ REALM=realm,
61
+ )
62
+
63
+
64
+ @timeit
65
+ def cleanup(
66
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
67
+ ) -> None:
68
+ GraphJob.from_node_schema(KeycloakUserSchema(), common_job_parameters).run(
69
+ neo4j_session
70
+ )