prelude-sdk-beta 1447__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.
@@ -0,0 +1,83 @@
1
+ import os
2
+ import requests
3
+
4
+ from requests.adapters import HTTPAdapter, Retry
5
+
6
+
7
+ PRELUDE_BACKOFF_FACTOR = int(os.getenv("PRELUDE_BACKOFF_FACTOR", 30))
8
+ PRELUDE_BACKOFF_TOTAL = int(os.getenv("PRELUDE_BACKOFF_TOTAL", 0))
9
+
10
+
11
+ class HttpController(object):
12
+ def __init__(self, account):
13
+ self._session = requests.Session()
14
+ self.account = account
15
+
16
+ retry = Retry(
17
+ total=PRELUDE_BACKOFF_TOTAL,
18
+ backoff_factor=PRELUDE_BACKOFF_FACTOR,
19
+ status_forcelist=[429],
20
+ )
21
+
22
+ self._session.mount("http://", HTTPAdapter(max_retries=retry))
23
+ self._session.mount("https://", HTTPAdapter(max_retries=retry))
24
+
25
+ def resolve_enums(self, data, enum_params: list[tuple]):
26
+ for [enum_class, key] in enum_params:
27
+ self._resolve_enum(data, enum_class, key)
28
+
29
+ def _resolve_enum(self, data, enum_class, key):
30
+ if isinstance(data, list):
31
+ for item in data:
32
+ if isinstance(item, dict):
33
+ self._resolve_enum(item, enum_class, key)
34
+ elif isinstance(data, dict):
35
+ for k, v in data.items():
36
+ if k == key:
37
+ if isinstance(v, list):
38
+ for i, item in enumerate(v):
39
+ v[i] = enum_class[item].name
40
+ elif v is not None:
41
+ data[k] = enum_class[v].name
42
+ elif isinstance(v, dict) or isinstance(v, list):
43
+ self._resolve_enum(v, enum_class, key)
44
+
45
+ def get(self, url, retry=True, **kwargs):
46
+ res = self._session.get(url, **kwargs)
47
+ if res.status_code == 200:
48
+ return res
49
+ if res.status_code == 401 and retry and self.account.token_location:
50
+ self.account.refresh_tokens()
51
+ self.account.update_auth_header()
52
+ return self.get(url, retry=False, **kwargs)
53
+ raise Exception(res.text)
54
+
55
+ def post(self, url, retry=True, **kwargs):
56
+ res = self._session.post(url, **kwargs)
57
+ if res.status_code == 200:
58
+ return res
59
+ if res.status_code == 401 and retry and self.account.token_location:
60
+ self.account.refresh_tokens()
61
+ self.account.update_auth_header()
62
+ return self.post(url, retry=False, **kwargs)
63
+ raise Exception(res.text)
64
+
65
+ def delete(self, url, retry=True, **kwargs):
66
+ res = self._session.delete(url, **kwargs)
67
+ if res.status_code == 200:
68
+ return res
69
+ if res.status_code == 401 and retry and self.account.token_location:
70
+ self.account.refresh_tokens()
71
+ self.account.update_auth_header()
72
+ return self.delete(url, retry=False, **kwargs)
73
+ raise Exception(res.text)
74
+
75
+ def put(self, url, retry=True, **kwargs):
76
+ res = self._session.put(url, **kwargs)
77
+ if res.status_code == 200:
78
+ return res
79
+ if res.status_code == 401 and retry and self.account.token_location:
80
+ self.account.refresh_tokens()
81
+ self.account.update_auth_header()
82
+ return self.put(url, retry=False, **kwargs)
83
+ raise Exception(res.text)
@@ -0,0 +1,285 @@
1
+ from prelude_sdk_beta.controllers.http_controller import HttpController
2
+ from prelude_sdk_beta.models.account import verify_credentials
3
+ from prelude_sdk_beta.models.codes import Control, Mode, Permission
4
+
5
+
6
+ class IAMAccountController(HttpController):
7
+
8
+ def __init__(self, account):
9
+ super().__init__(account)
10
+
11
+ @verify_credentials
12
+ def get_account(self):
13
+ """Get account properties"""
14
+ res = self.get(
15
+ f"{self.account.hq}/iam/account", headers=self.account.headers, timeout=10
16
+ )
17
+ account = res.json()
18
+ if self.account.resolve_enums:
19
+ self.resolve_enums(
20
+ account, [(Mode, "mode"), (Permission, "permission"), (Control, "id")]
21
+ )
22
+ return account
23
+
24
+ @verify_credentials
25
+ def purge_account(self):
26
+ """Delete an account and all things in it"""
27
+ res = self.delete(
28
+ f"{self.account.hq}/iam/account", headers=self.account.headers, timeout=10
29
+ )
30
+ return res.json()
31
+
32
+ @verify_credentials
33
+ def update_account(self, mode: Mode = None, company: str = None, slug: str = None):
34
+ """Update properties on an account"""
35
+ body = dict()
36
+ if mode is not None:
37
+ body["mode"] = mode.name
38
+ if company is not None:
39
+ body["company"] = company
40
+ if slug is not None:
41
+ body["slug"] = slug
42
+
43
+ res = self.put(
44
+ f"{self.account.hq}/iam/account",
45
+ headers=self.account.headers,
46
+ json=body,
47
+ timeout=10,
48
+ )
49
+ return res.json()
50
+
51
+ @verify_credentials
52
+ def attach_oidc(
53
+ self,
54
+ client_id: str,
55
+ client_secret: str,
56
+ issuer: str,
57
+ oidc_url: str,
58
+ ):
59
+ """Attach OIDC to an account"""
60
+ email_attr = "email"
61
+ if issuer == "azure":
62
+ email_attr = "upn"
63
+ body = dict(
64
+ client_id=client_id,
65
+ client_secret=client_secret,
66
+ email_attr=email_attr,
67
+ issuer=issuer,
68
+ oidc_url=oidc_url,
69
+ )
70
+
71
+ res = self.post(
72
+ f"{self.account.hq}/iam/account/oidc",
73
+ headers=self.account.headers,
74
+ json=body,
75
+ timeout=10,
76
+ )
77
+ return res.json()
78
+
79
+ @verify_credentials
80
+ def detach_oidc(self):
81
+ """Detach OIDC to an account"""
82
+ res = self.delete(
83
+ f"{self.account.hq}/iam/account/oidc",
84
+ headers=self.account.headers,
85
+ timeout=10,
86
+ )
87
+ return res.json()
88
+
89
+ @verify_credentials
90
+ def invite_user(
91
+ self,
92
+ email: str,
93
+ oidc: str | None,
94
+ permission: Permission,
95
+ name: str | None = None,
96
+ ):
97
+ """Invite a new user to the account"""
98
+ body = dict(permission=permission.name, handle=email, oidc=oidc)
99
+ if name:
100
+ body["name"] = name
101
+
102
+ res = self.post(
103
+ url=f"{self.account.hq}/iam/account/user",
104
+ json=body,
105
+ headers=self.account.headers,
106
+ timeout=10,
107
+ )
108
+ user = res.json()
109
+ if self.account.resolve_enums:
110
+ self.resolve_enums(user, [(Permission, "permission")])
111
+ return user
112
+
113
+ @verify_credentials
114
+ def create_service_user(self, name: str):
115
+ """Create a service user"""
116
+ body = dict(name=name)
117
+
118
+ res = self.post(
119
+ f"{self.account.hq}/iam/account/service_user",
120
+ json=body,
121
+ headers=self.account.headers,
122
+ timeout=10,
123
+ )
124
+ return res.json()
125
+
126
+ @verify_credentials
127
+ def delete_service_user(self, handle: str):
128
+ """Delete service user"""
129
+ body = dict(handle=handle)
130
+
131
+ res = self.delete(
132
+ f"{self.account.hq}/iam/account/service_user",
133
+ json=body,
134
+ headers=self.account.headers,
135
+ timeout=10,
136
+ )
137
+ return res.json()
138
+
139
+ @verify_credentials
140
+ def update_account_user(
141
+ self,
142
+ email: str,
143
+ oidc: str | None,
144
+ permission: Permission = None,
145
+ ):
146
+ """Update properties on an account user"""
147
+ body = dict(handle=email, oidc=oidc)
148
+ if permission is not None:
149
+ body["permission"] = permission.name
150
+
151
+ res = self.put(
152
+ f"{self.account.hq}/iam/account/user",
153
+ json=body,
154
+ headers=self.account.headers,
155
+ timeout=10,
156
+ )
157
+ return res.json()
158
+
159
+ @verify_credentials
160
+ def remove_user(self, email: str, oidc: str | None):
161
+ """Remove user from the account"""
162
+ params = dict(handle=email, oidc=oidc)
163
+
164
+ res = self.delete(
165
+ f"{self.account.hq}/iam/account/user",
166
+ params=params,
167
+ headers=self.account.headers,
168
+ timeout=10,
169
+ )
170
+ return res.json()
171
+
172
+ @verify_credentials
173
+ def audit_logs(self, days: int = 7, limit: int = 1000):
174
+ """Get audit logs from the last X days"""
175
+ params = dict(days=days, limit=limit)
176
+ res = self.get(
177
+ f"{self.account.hq}/iam/audit",
178
+ headers=self.account.headers,
179
+ params=params,
180
+ timeout=30,
181
+ )
182
+ return res.json()
183
+
184
+ def sign_up(self, company, email, name):
185
+ """(NOT AVAIABLE IN PRODUCTION) Create a new user and account"""
186
+ body = dict(company=company, email=email, name=name)
187
+
188
+ res = self._session.post(
189
+ f"{self.account.hq}/iam/new_user_and_account",
190
+ headers=self.account.headers,
191
+ json=body,
192
+ timeout=20,
193
+ )
194
+ data = res.json()
195
+ if self.account.profile:
196
+ self.account.keychain.configure_keychain(
197
+ account=data["account_id"],
198
+ handle=data["user_id"],
199
+ hq=self.account.hq,
200
+ profile=self.account.profile,
201
+ )
202
+ return data
203
+
204
+
205
+ class IAMUserController(HttpController):
206
+
207
+ def __init__(self, account):
208
+ super().__init__(account)
209
+
210
+ @verify_credentials
211
+ def list_accounts(self):
212
+ """List all accounts for your user"""
213
+ res = self.get(
214
+ f"{self.account.hq}/iam/user/account",
215
+ headers=self.account.headers,
216
+ timeout=10,
217
+ )
218
+ return res.json()
219
+
220
+ @verify_credentials
221
+ def purge_user(self):
222
+ """Delete your user"""
223
+ res = self.delete(
224
+ f"{self.account.hq}/iam/user", headers=self.account.headers, timeout=10
225
+ )
226
+ return res.json()
227
+
228
+ @verify_credentials
229
+ def update_user(
230
+ self,
231
+ name: str = None,
232
+ ):
233
+ """Update properties on a user"""
234
+ body = dict()
235
+ if name is not None:
236
+ body["name"] = name
237
+
238
+ res = self.put(
239
+ f"{self.account.hq}/iam/user",
240
+ json=body,
241
+ headers=self.account.headers,
242
+ timeout=10,
243
+ )
244
+ return res.json()
245
+
246
+ def forgot_password(self):
247
+ """Send a forgot password email"""
248
+ body = dict(handle=self.account.handle)
249
+
250
+ res = self.post(
251
+ f"{self.account.hq}/iam/user/forgot_password",
252
+ json=body,
253
+ headers=self.account.headers,
254
+ timeout=10,
255
+ )
256
+ return res.json()
257
+
258
+ def confirm_forgot_password(self, confirmation_code: str, new_password: str):
259
+ """Change a password using confirmation code"""
260
+ body = dict(
261
+ handle=self.account.handle,
262
+ confirmation_code=confirmation_code,
263
+ password=new_password,
264
+ )
265
+
266
+ res = self.post(
267
+ f"{self.account.hq}/iam/user/forgot_password",
268
+ json=body,
269
+ headers=self.account.headers,
270
+ timeout=10,
271
+ )
272
+ return res.json()
273
+
274
+ @verify_credentials
275
+ def change_password(self, current_password: str, new_password: str):
276
+ """Change your password"""
277
+ body = dict(current_password=current_password, new_password=new_password)
278
+
279
+ res = self.post(
280
+ f"{self.account.hq}/iam/user/change_password",
281
+ json=body,
282
+ headers=self.account.headers,
283
+ timeout=10,
284
+ )
285
+ return res.json()
@@ -0,0 +1,37 @@
1
+ from itertools import chain
2
+
3
+ from prelude_sdk_beta.controllers.http_controller import HttpController
4
+ from prelude_sdk_beta.models.account import verify_credentials
5
+ from prelude_sdk_beta.models.codes import BackgroundJobTypes, Control
6
+
7
+
8
+ class JobsController(HttpController):
9
+
10
+ def __init__(self, account):
11
+ super().__init__(account)
12
+
13
+ @verify_credentials
14
+ def job_statuses(self):
15
+ """Get job statuses"""
16
+ res = self.get(
17
+ f"{self.account.hq}/jobs/statuses", headers=self.account.headers, timeout=30
18
+ )
19
+ jobs = res.json()
20
+ if self.account.resolve_enums:
21
+ self.resolve_enums(jobs, [(Control, "control")])
22
+ return jobs
23
+
24
+ @verify_credentials
25
+ def job_status(self, job_id: str):
26
+ """Get job status given job ID"""
27
+ res = self.get(
28
+ f"{self.account.hq}/jobs/statuses/{job_id}",
29
+ headers=self.account.headers,
30
+ timeout=30,
31
+ )
32
+ job = res.json()
33
+ if self.account.resolve_enums:
34
+ self.resolve_enums(
35
+ job, [(Control, "control"), (BackgroundJobTypes, "job_type")]
36
+ )
37
+ return job
@@ -0,0 +1,154 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from prelude_sdk_beta.controllers.http_controller import HttpController
4
+ from prelude_sdk_beta.models.account import verify_credentials
5
+ from prelude_sdk_beta.models.codes import Control
6
+
7
+
8
+ class PartnerController(HttpController):
9
+
10
+ def __init__(self, account):
11
+ super().__init__(account)
12
+
13
+ @verify_credentials
14
+ def attach(
15
+ self,
16
+ partner: Control,
17
+ api: str,
18
+ user: str,
19
+ secret: str,
20
+ name: str | None = None,
21
+ instance_id: str | None = None,
22
+ ):
23
+ """Attach a partner to your account"""
24
+ params = dict()
25
+ if name:
26
+ params["name"] = name
27
+ if api:
28
+ params["api"] = api
29
+ if user:
30
+ params["user"] = user
31
+ if secret:
32
+ params["secret"] = secret
33
+ extra = f"/{instance_id}" if instance_id else ""
34
+ res = self.post(
35
+ f"{self.account.hq}/partner/{partner.name}{extra}",
36
+ headers=self.account.headers,
37
+ json=params,
38
+ timeout=10,
39
+ )
40
+ return res.json()
41
+
42
+ @verify_credentials
43
+ def detach(self, partner: Control, instance_id: str):
44
+ """Detach a partner from your Detect account"""
45
+ res = self.delete(
46
+ f"{self.account.hq}/partner/{partner.name}/{instance_id}",
47
+ headers=self.account.headers,
48
+ timeout=30,
49
+ )
50
+ return res.json()
51
+
52
+ @verify_credentials
53
+ def block(self, partner: Control, test_id: str):
54
+ """Report to a partner to block a test"""
55
+ params = dict(test_id=test_id)
56
+ res = self.post(
57
+ f"{self.account.hq}/partner/block/{partner.name}",
58
+ headers=self.account.headers,
59
+ json=params,
60
+ timeout=30,
61
+ )
62
+ return res.json()
63
+
64
+ @verify_credentials
65
+ def endpoints(
66
+ self,
67
+ partner: Control,
68
+ platform: str,
69
+ hostname: str = "",
70
+ offset: int = 0,
71
+ count: int = 100,
72
+ ):
73
+ """Get a list of endpoints from a partner"""
74
+ params = dict(platform=platform, hostname=hostname, offset=offset, count=count)
75
+ res = self.get(
76
+ f"{self.account.hq}/partner/endpoints/{partner.name}",
77
+ headers=self.account.headers,
78
+ params=params,
79
+ timeout=30,
80
+ )
81
+ return res.json()
82
+
83
+ @verify_credentials
84
+ def deploy(self, partner: Control, host_ids: list):
85
+ """Deploy probes on all specified partner endpoints"""
86
+ params = dict(host_ids=host_ids)
87
+ res = self.post(
88
+ f"{self.account.hq}/partner/deploy/{partner.name}",
89
+ headers=self.account.headers,
90
+ json=params,
91
+ timeout=30,
92
+ )
93
+ return res.json()
94
+
95
+ @verify_credentials
96
+ def list_reports(self, partner: Control, test_id: str | None):
97
+ """Get reports to a partner for a test"""
98
+ params = dict(test_id=test_id) if test_id else dict()
99
+ res = self.get(
100
+ f"{self.account.hq}/partner/reports/{partner.name}",
101
+ headers=self.account.headers,
102
+ json=params,
103
+ timeout=30,
104
+ )
105
+ return res.json()
106
+
107
+ @verify_credentials
108
+ def observed_detected(self, test_id: str | None = None, hours: int | None = None):
109
+ """Get observed_detected stats"""
110
+ params = dict()
111
+ if test_id:
112
+ params["test_id"] = test_id
113
+ if hours:
114
+ params["start_epoch_ms"] = (
115
+ datetime.now(timezone.utc).timestamp() - hours * 60 * 60
116
+ ) * 1000
117
+
118
+ res = self.get(
119
+ f"{self.account.hq}/partner/observed_detected",
120
+ headers=self.account.headers,
121
+ json=params,
122
+ timeout=30,
123
+ )
124
+ return res.json()
125
+
126
+ @verify_credentials
127
+ def list_advisories(
128
+ self, partner: Control, start: str = None, limit: int = None, offset: int = None
129
+ ):
130
+ """Get advisory reports provided by a partner"""
131
+ params = dict()
132
+ if start:
133
+ params["start"] = start
134
+ if limit:
135
+ params["limit"] = limit
136
+ if offset:
137
+ params["offset"] = offset
138
+ res = self.get(
139
+ f"{self.account.hq}/partner/advisories/{partner.name}",
140
+ headers=self.account.headers,
141
+ params=params,
142
+ timeout=30,
143
+ )
144
+ return res.json()
145
+
146
+ @verify_credentials
147
+ def partner_groups(self, partner: Control, instance_id: str):
148
+ """Get a list of partner groups"""
149
+ res = self.get(
150
+ f"{self.account.hq}/partner/groups/{partner.name}/{instance_id}",
151
+ headers=self.account.headers,
152
+ timeout=30,
153
+ )
154
+ return res.json()
@@ -0,0 +1,14 @@
1
+ from prelude_sdk_beta.controllers.http_controller import HttpController
2
+
3
+
4
+ class ProbeController(HttpController):
5
+
6
+ def __init__(self, account):
7
+ super().__init__(account)
8
+
9
+ def download(self, name: str, dos: str):
10
+ """Download a probe executable"""
11
+ res = self.get(
12
+ f"{self.account.hq}/download/{name}", headers=dict(dos=dos), timeout=10
13
+ )
14
+ return res.text