cartography 0.103.0rc1__py3-none-any.whl → 0.104.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 (75) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +97 -1
  3. cartography/config.py +28 -0
  4. cartography/graph/cleanupbuilder.py +151 -41
  5. cartography/intel/anthropic/__init__.py +62 -0
  6. cartography/intel/anthropic/apikeys.py +72 -0
  7. cartography/intel/anthropic/users.py +75 -0
  8. cartography/intel/anthropic/util.py +51 -0
  9. cartography/intel/anthropic/workspaces.py +95 -0
  10. cartography/intel/aws/cloudtrail.py +3 -38
  11. cartography/intel/aws/cloudwatch.py +93 -0
  12. cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
  13. cartography/intel/aws/resources.py +2 -0
  14. cartography/intel/aws/secretsmanager.py +150 -3
  15. cartography/intel/aws/ssm.py +71 -0
  16. cartography/intel/cloudflare/__init__.py +74 -0
  17. cartography/intel/cloudflare/accounts.py +57 -0
  18. cartography/intel/cloudflare/dnsrecords.py +64 -0
  19. cartography/intel/cloudflare/members.py +75 -0
  20. cartography/intel/cloudflare/roles.py +65 -0
  21. cartography/intel/cloudflare/zones.py +64 -0
  22. cartography/intel/entra/ou.py +21 -5
  23. cartography/intel/openai/__init__.py +86 -0
  24. cartography/intel/openai/adminapikeys.py +89 -0
  25. cartography/intel/openai/apikeys.py +96 -0
  26. cartography/intel/openai/projects.py +97 -0
  27. cartography/intel/openai/serviceaccounts.py +82 -0
  28. cartography/intel/openai/users.py +75 -0
  29. cartography/intel/openai/util.py +45 -0
  30. cartography/intel/tailscale/__init__.py +77 -0
  31. cartography/intel/tailscale/acls.py +146 -0
  32. cartography/intel/tailscale/devices.py +127 -0
  33. cartography/intel/tailscale/postureintegrations.py +81 -0
  34. cartography/intel/tailscale/tailnets.py +76 -0
  35. cartography/intel/tailscale/users.py +80 -0
  36. cartography/intel/tailscale/utils.py +132 -0
  37. cartography/models/anthropic/__init__.py +0 -0
  38. cartography/models/anthropic/apikey.py +90 -0
  39. cartography/models/anthropic/organization.py +19 -0
  40. cartography/models/anthropic/user.py +48 -0
  41. cartography/models/anthropic/workspace.py +90 -0
  42. cartography/models/aws/cloudtrail/trail.py +24 -0
  43. cartography/models/aws/cloudwatch/__init__.py +0 -0
  44. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  45. cartography/models/aws/secretsmanager/__init__.py +0 -0
  46. cartography/models/aws/secretsmanager/secret_version.py +116 -0
  47. cartography/models/aws/ssm/parameters.py +84 -0
  48. cartography/models/cloudflare/__init__.py +0 -0
  49. cartography/models/cloudflare/account.py +25 -0
  50. cartography/models/cloudflare/dnsrecord.py +55 -0
  51. cartography/models/cloudflare/member.py +82 -0
  52. cartography/models/cloudflare/role.py +44 -0
  53. cartography/models/cloudflare/zone.py +59 -0
  54. cartography/models/core/nodes.py +15 -2
  55. cartography/models/openai/__init__.py +0 -0
  56. cartography/models/openai/adminapikey.py +90 -0
  57. cartography/models/openai/apikey.py +84 -0
  58. cartography/models/openai/organization.py +17 -0
  59. cartography/models/openai/project.py +89 -0
  60. cartography/models/openai/serviceaccount.py +50 -0
  61. cartography/models/openai/user.py +49 -0
  62. cartography/models/tailscale/__init__.py +0 -0
  63. cartography/models/tailscale/device.py +95 -0
  64. cartography/models/tailscale/group.py +86 -0
  65. cartography/models/tailscale/postureintegration.py +58 -0
  66. cartography/models/tailscale/tag.py +102 -0
  67. cartography/models/tailscale/tailnet.py +29 -0
  68. cartography/models/tailscale/user.py +52 -0
  69. cartography/sync.py +8 -0
  70. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/METADATA +8 -4
  71. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/RECORD +75 -19
  72. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/WHEEL +1 -1
  73. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/entry_points.txt +0 -0
  74. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/licenses/LICENSE +0 -0
  75. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,64 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ from cloudflare import Cloudflare
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.cloudflare.dnsrecord import CloudflareDNSRecordSchema
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
+ client: Cloudflare,
23
+ common_job_parameters: Dict[str, Any],
24
+ zone_id: str,
25
+ ) -> None:
26
+ dnsrecords = get(client, zone_id)
27
+ load_dnsrecords(
28
+ neo4j_session,
29
+ dnsrecords,
30
+ zone_id,
31
+ common_job_parameters["UPDATE_TAG"],
32
+ )
33
+ cleanup(neo4j_session, common_job_parameters)
34
+
35
+
36
+ @timeit
37
+ def get(client: Cloudflare, zone_id: str) -> List[Dict[str, Any]]:
38
+ return [
39
+ dnsrecord.to_dict() for dnsrecord in client.dns.records.list(zone_id=zone_id)
40
+ ]
41
+
42
+
43
+ def load_dnsrecords(
44
+ neo4j_session: neo4j.Session,
45
+ data: List[Dict[str, Any]],
46
+ zone_id: str,
47
+ update_tag: int,
48
+ ) -> None:
49
+ logger.info("Loading %d Cloudflare DNSRecords into Neo4j.", len(data))
50
+ load(
51
+ neo4j_session,
52
+ CloudflareDNSRecordSchema(),
53
+ data,
54
+ lastupdated=update_tag,
55
+ zone_id=zone_id,
56
+ )
57
+
58
+
59
+ def cleanup(
60
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
61
+ ) -> None:
62
+ GraphJob.from_node_schema(CloudflareDNSRecordSchema(), common_job_parameters).run(
63
+ neo4j_session
64
+ )
@@ -0,0 +1,75 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ from cloudflare import Cloudflare
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.cloudflare.member import CloudflareMemberSchema
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
+ client: Cloudflare,
23
+ common_job_parameters: Dict[str, Any],
24
+ account_id: str,
25
+ ) -> None:
26
+ members = get(client, account_id)
27
+ transformed_members = transform_members(members)
28
+ load_members(
29
+ neo4j_session,
30
+ transformed_members,
31
+ account_id,
32
+ common_job_parameters["UPDATE_TAG"],
33
+ )
34
+ cleanup(neo4j_session, common_job_parameters)
35
+
36
+
37
+ @timeit
38
+ def get(client: Cloudflare, account_id: str) -> List[Dict[str, Any]]:
39
+ return [
40
+ member.to_dict()
41
+ for member in client.accounts.members.list(account_id=account_id)
42
+ ]
43
+
44
+
45
+ def load_members(
46
+ neo4j_session: neo4j.Session,
47
+ data: List[Dict[str, Any]],
48
+ account_id: str,
49
+ update_tag: int,
50
+ ) -> None:
51
+ logger.info("Loading %d Cloudflare members into Neo4j.", len(data))
52
+ load(
53
+ neo4j_session,
54
+ CloudflareMemberSchema(),
55
+ data,
56
+ lastupdated=update_tag,
57
+ account_id=account_id,
58
+ )
59
+
60
+
61
+ def transform_members(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
62
+ result: List[Dict[str, Any]] = []
63
+ for member in data:
64
+ member["roles_ids"] = [role["id"] for role in member.get("roles", [])]
65
+ member["policies_ids"] = [policy["id"] for policy in member.get("policies", [])]
66
+ result.append(member)
67
+ return result
68
+
69
+
70
+ def cleanup(
71
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
72
+ ) -> None:
73
+ GraphJob.from_node_schema(CloudflareMemberSchema(), common_job_parameters).run(
74
+ neo4j_session
75
+ )
@@ -0,0 +1,65 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ from cloudflare import Cloudflare
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.cloudflare.role import CloudflareRoleSchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @timeit
18
+ def sync(
19
+ neo4j_session: neo4j.Session,
20
+ client: Cloudflare,
21
+ common_job_parameters: Dict[str, Any],
22
+ account_id: str,
23
+ ) -> None:
24
+ roles = get(client, account_id)
25
+ load_roles(
26
+ neo4j_session,
27
+ roles,
28
+ account_id,
29
+ common_job_parameters["UPDATE_TAG"],
30
+ )
31
+ cleanup(neo4j_session, common_job_parameters)
32
+
33
+
34
+ @timeit
35
+ def get(
36
+ client: Cloudflare,
37
+ account_id: str,
38
+ ) -> List[Dict[str, Any]]:
39
+ return [
40
+ role.to_dict() for role in client.accounts.roles.list(account_id=account_id)
41
+ ]
42
+
43
+
44
+ def load_roles(
45
+ neo4j_session: neo4j.Session,
46
+ data: List[Dict[str, Any]],
47
+ account_id: str,
48
+ update_tag: int,
49
+ ) -> None:
50
+ logger.info("Loading %d Cloudflare roles into Neo4j.", len(data))
51
+ load(
52
+ neo4j_session,
53
+ CloudflareRoleSchema(),
54
+ data,
55
+ lastupdated=update_tag,
56
+ account_id=account_id,
57
+ )
58
+
59
+
60
+ def cleanup(
61
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
62
+ ) -> None:
63
+ GraphJob.from_node_schema(CloudflareRoleSchema(), common_job_parameters).run(
64
+ neo4j_session
65
+ )
@@ -0,0 +1,64 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ from cloudflare import Cloudflare
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.cloudflare.zone import CloudflareZoneSchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @timeit
18
+ def sync(
19
+ neo4j_session: neo4j.Session,
20
+ client: Cloudflare,
21
+ common_job_parameters: Dict[str, Any],
22
+ account_id: str,
23
+ ) -> List[Dict]:
24
+ zones = get(client, account_id)
25
+ load_zones(
26
+ neo4j_session,
27
+ zones,
28
+ account_id,
29
+ common_job_parameters["UPDATE_TAG"],
30
+ )
31
+ cleanup(neo4j_session, common_job_parameters)
32
+ return zones
33
+
34
+
35
+ @timeit
36
+ def get(
37
+ client: Cloudflare,
38
+ account_id: str,
39
+ ) -> List[Dict[str, Any]]:
40
+ return [zone.to_dict() for zone in client.zones.list(account=account_id)]
41
+
42
+
43
+ def load_zones(
44
+ neo4j_session: neo4j.Session,
45
+ data: List[Dict[str, Any]],
46
+ account_id: str,
47
+ update_tag: int,
48
+ ) -> None:
49
+ logger.info("Loading %d Cloudflare zones into Neo4j.", len(data))
50
+ load(
51
+ neo4j_session,
52
+ CloudflareZoneSchema(),
53
+ data,
54
+ lastupdated=update_tag,
55
+ account_id=account_id,
56
+ )
57
+
58
+
59
+ def cleanup(
60
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
61
+ ) -> None:
62
+ GraphJob.from_node_schema(CloudflareZoneSchema(), common_job_parameters).run(
63
+ neo4j_session
64
+ )
@@ -22,12 +22,28 @@ async def get_entra_ous(client: GraphServiceClient) -> list[AdministrativeUnit]:
22
22
  Get all OUs from Microsoft Graph API with pagination support
23
23
  """
24
24
  all_units: list[AdministrativeUnit] = []
25
- request = client.directory.administrative_units.request()
26
25
 
27
- while request:
28
- response = await request.get()
29
- all_units.extend(response.value)
30
- request = response.odata_next_link if response.odata_next_link else None
26
+ # Initialize first page request
27
+ current_request = client.directory.administrative_units
28
+
29
+ while current_request:
30
+ try:
31
+ response = await current_request.get()
32
+ if response and response.value:
33
+ all_units.extend(response.value)
34
+
35
+ # Handle next page using OData link
36
+ if response.odata_next_link:
37
+ current_request = client.directory.administrative_units.with_url(
38
+ response.odata_next_link
39
+ )
40
+ else:
41
+ current_request = None
42
+ else:
43
+ current_request = None
44
+ except Exception as e:
45
+ logger.error(f"Failed to retrieve administrative units: {str(e)}")
46
+ current_request = None
31
47
 
32
48
  return all_units
33
49
 
@@ -0,0 +1,86 @@
1
+ import logging
2
+
3
+ import neo4j
4
+ import requests
5
+
6
+ import cartography.intel.openai.adminapikeys
7
+ import cartography.intel.openai.apikeys
8
+ import cartography.intel.openai.projects
9
+ import cartography.intel.openai.serviceaccounts
10
+ import cartography.intel.openai.users
11
+ from cartography.config import Config
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @timeit
18
+ def start_openai_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
19
+ """
20
+ If this module is configured, perform ingestion of OpenAI data. Otherwise warn and exit
21
+ :param neo4j_session: Neo4J session for database interface
22
+ :param config: A cartography.config object
23
+ :return: None
24
+ """
25
+
26
+ if not config.openai_apikey or not config.openai_org_id:
27
+ logger.info(
28
+ "OpenAI import is not configured - skipping this module. "
29
+ "See docs to configure.",
30
+ )
31
+ return
32
+
33
+ # Create requests sessions
34
+ api_session = requests.session()
35
+ api_session.headers.update(
36
+ {
37
+ "Authorization": f"Bearer {config.openai_apikey}",
38
+ "OpenAI-Organization": config.openai_org_id,
39
+ }
40
+ )
41
+
42
+ common_job_parameters = {
43
+ "UPDATE_TAG": config.update_tag,
44
+ "BASE_URL": "https://api.openai.com/v1",
45
+ "ORG_ID": config.openai_org_id,
46
+ }
47
+
48
+ # Organization node is created during the users sync
49
+ cartography.intel.openai.users.sync(
50
+ neo4j_session,
51
+ api_session,
52
+ common_job_parameters,
53
+ ORG_ID=config.openai_org_id,
54
+ )
55
+
56
+ for project in cartography.intel.openai.projects.sync(
57
+ neo4j_session,
58
+ api_session,
59
+ common_job_parameters,
60
+ ORG_ID=config.openai_org_id,
61
+ ):
62
+ project_job_parameters = {
63
+ "UPDATE_TAG": config.update_tag,
64
+ "BASE_URL": "https://api.openai.com/v1",
65
+ "ORG_ID": config.openai_org_id,
66
+ "project_id": project["id"],
67
+ }
68
+ cartography.intel.openai.serviceaccounts.sync(
69
+ neo4j_session,
70
+ api_session,
71
+ project_job_parameters,
72
+ project_id=project["id"],
73
+ )
74
+ cartography.intel.openai.apikeys.sync(
75
+ neo4j_session,
76
+ api_session,
77
+ project_job_parameters,
78
+ project_id=project["id"],
79
+ )
80
+
81
+ cartography.intel.openai.adminapikeys.sync(
82
+ neo4j_session,
83
+ api_session,
84
+ common_job_parameters,
85
+ ORG_ID=config.openai_org_id,
86
+ )
@@ -0,0 +1,89 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.openai.util import paginated_get
12
+ from cartography.models.openai.adminapikey import OpenAIAdminApiKeySchema
13
+ from cartography.util import timeit
14
+
15
+ logger = logging.getLogger(__name__)
16
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
17
+ _TIMEOUT = (60, 60)
18
+
19
+
20
+ @timeit
21
+ def sync(
22
+ neo4j_session: neo4j.Session,
23
+ api_session: requests.Session,
24
+ common_job_parameters: Dict[str, Any],
25
+ ORG_ID: str,
26
+ ) -> None:
27
+ adminapikeys = get(
28
+ api_session,
29
+ common_job_parameters["BASE_URL"],
30
+ )
31
+ transformed_adminapikeys = transform(adminapikeys)
32
+ load_adminapikeys(
33
+ neo4j_session,
34
+ transformed_adminapikeys,
35
+ ORG_ID,
36
+ common_job_parameters["UPDATE_TAG"],
37
+ )
38
+ cleanup(neo4j_session, common_job_parameters)
39
+
40
+
41
+ @timeit
42
+ def get(
43
+ api_session: requests.Session,
44
+ base_url: str,
45
+ ) -> List[Dict[str, Any]]:
46
+ return list(
47
+ paginated_get(
48
+ api_session, f"{base_url}/organization/admin_api_keys", timeout=_TIMEOUT
49
+ )
50
+ )
51
+
52
+
53
+ def transform(
54
+ adminapikeys: List[Dict[str, Any]],
55
+ ) -> List[Dict[str, Any]]:
56
+ result: List[Dict[str, Any]] = []
57
+ for adminapikey in adminapikeys:
58
+ if adminapikey["owner"]["type"] == "user":
59
+ adminapikey["owner_user_id"] = adminapikey["owner"]["id"]
60
+ else:
61
+ adminapikey["owner_sa_id"] = adminapikey["owner"]["id"]
62
+ result.append(adminapikey)
63
+ return result
64
+
65
+
66
+ @timeit
67
+ def load_adminapikeys(
68
+ neo4j_session: neo4j.Session,
69
+ data: List[Dict[str, Any]],
70
+ ORG_ID: str,
71
+ update_tag: int,
72
+ ) -> None:
73
+ logger.info("Loading %d OpenAI AdminApiKey into Neo4j.", len(data))
74
+ load(
75
+ neo4j_session,
76
+ OpenAIAdminApiKeySchema(),
77
+ data,
78
+ lastupdated=update_tag,
79
+ ORG_ID=ORG_ID,
80
+ )
81
+
82
+
83
+ @timeit
84
+ def cleanup(
85
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
86
+ ) -> None:
87
+ GraphJob.from_node_schema(OpenAIAdminApiKeySchema(), common_job_parameters).run(
88
+ neo4j_session
89
+ )
@@ -0,0 +1,96 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.openai.util import paginated_get
12
+ from cartography.models.openai.apikey import OpenAIApiKeySchema
13
+ from cartography.util import timeit
14
+
15
+ logger = logging.getLogger(__name__)
16
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
17
+ _TIMEOUT = (60, 60)
18
+
19
+
20
+ @timeit
21
+ def sync(
22
+ neo4j_session: neo4j.Session,
23
+ api_session: requests.Session,
24
+ common_job_parameters: Dict[str, Any],
25
+ project_id: str,
26
+ ) -> None:
27
+ apikeys = get(
28
+ api_session,
29
+ common_job_parameters["BASE_URL"],
30
+ project_id,
31
+ )
32
+ transformed_apikeys = transform(apikeys)
33
+ load_apikeys(
34
+ neo4j_session,
35
+ transformed_apikeys,
36
+ project_id,
37
+ common_job_parameters["UPDATE_TAG"],
38
+ )
39
+ cleanup(neo4j_session, common_job_parameters)
40
+
41
+
42
+ @timeit
43
+ def get(
44
+ api_session: requests.Session,
45
+ base_url: str,
46
+ project_id: str,
47
+ ) -> List[Dict[str, Any]]:
48
+ return list(
49
+ paginated_get(
50
+ api_session,
51
+ "{base_url}/organization/projects/{project_id}/api_keys".format(
52
+ base_url=base_url,
53
+ project_id=project_id,
54
+ ),
55
+ timeout=_TIMEOUT,
56
+ )
57
+ )
58
+
59
+
60
+ def transform(
61
+ apikeys: List[Dict[str, Any]],
62
+ ) -> List[Dict[str, Any]]:
63
+ result: List[Dict[str, Any]] = []
64
+ for apikey in apikeys:
65
+ if apikey["owner"]["type"] == "user":
66
+ apikey["owner_user_id"] = apikey["owner"]["user"]["id"]
67
+ else:
68
+ apikey["owner_sa_id"] = apikey["owner"]["service_account"]["id"]
69
+ result.append(apikey)
70
+ return result
71
+
72
+
73
+ @timeit
74
+ def load_apikeys(
75
+ neo4j_session: neo4j.Session,
76
+ data: List[Dict[str, Any]],
77
+ project_id: str,
78
+ update_tag: int,
79
+ ) -> None:
80
+ logger.info("Loading %d OpenAI APIKey into Neo4j.", len(data))
81
+ load(
82
+ neo4j_session,
83
+ OpenAIApiKeySchema(),
84
+ data,
85
+ lastupdated=update_tag,
86
+ project_id=project_id,
87
+ )
88
+
89
+
90
+ @timeit
91
+ def cleanup(
92
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
93
+ ) -> None:
94
+ GraphJob.from_node_schema(OpenAIApiKeySchema(), common_job_parameters).run(
95
+ neo4j_session
96
+ )
@@ -0,0 +1,97 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.openai.util import paginated_get
12
+ from cartography.models.openai.project import OpenAIProjectSchema
13
+ from cartography.util import timeit
14
+
15
+ logger = logging.getLogger(__name__)
16
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
17
+ _TIMEOUT = (60, 60)
18
+
19
+
20
+ @timeit
21
+ def sync(
22
+ neo4j_session: neo4j.Session,
23
+ api_session: requests.Session,
24
+ common_job_parameters: Dict[str, Any],
25
+ ORG_ID: str,
26
+ ) -> List[Dict]:
27
+ projects = get(
28
+ api_session,
29
+ common_job_parameters["BASE_URL"],
30
+ )
31
+ for project in projects:
32
+ project["users"] = []
33
+ project["admins"] = []
34
+ for user in get_project_users(
35
+ api_session,
36
+ common_job_parameters["BASE_URL"],
37
+ project["id"],
38
+ ):
39
+ project["users"].append(user["id"])
40
+ if user["role"] == "owner":
41
+ project["admins"].append(user["id"])
42
+ load_projects(neo4j_session, projects, ORG_ID, common_job_parameters["UPDATE_TAG"])
43
+ cleanup(neo4j_session, common_job_parameters)
44
+ return projects
45
+
46
+
47
+ @timeit
48
+ def get(
49
+ api_session: requests.Session,
50
+ base_url: str,
51
+ ) -> List[Dict[str, Any]]:
52
+ return list(
53
+ paginated_get(
54
+ api_session, f"{base_url}/organization/projects", timeout=_TIMEOUT
55
+ )
56
+ )
57
+
58
+
59
+ @timeit
60
+ def get_project_users(
61
+ api_session: requests.Session,
62
+ base_url: str,
63
+ project_id: str,
64
+ ) -> List[Dict[str, Any]]:
65
+ return list(
66
+ paginated_get(
67
+ api_session,
68
+ f"{base_url}/organization/projects/{project_id}/users",
69
+ timeout=_TIMEOUT,
70
+ )
71
+ )
72
+
73
+
74
+ @timeit
75
+ def load_projects(
76
+ neo4j_session: neo4j.Session,
77
+ data: List[Dict[str, Any]],
78
+ ORG_ID: str,
79
+ update_tag: int,
80
+ ) -> None:
81
+ logger.info("Loading %d OpenAI Projects into Neo4j.", len(data))
82
+ load(
83
+ neo4j_session,
84
+ OpenAIProjectSchema(),
85
+ data,
86
+ lastupdated=update_tag,
87
+ ORG_ID=ORG_ID,
88
+ )
89
+
90
+
91
+ @timeit
92
+ def cleanup(
93
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
94
+ ) -> None:
95
+ GraphJob.from_node_schema(OpenAIProjectSchema(), common_job_parameters).run(
96
+ neo4j_session
97
+ )